squarehippo10
squarehippo10

Reputation: 1945

SwiftUI - How to call function with completion from view model

I have a form that gets a zip code and passes it to a method. The method looks up the city and state and returns the information as a tuple. The method also has a completion handler so that the rest of the form data isn't saved until the city and state are found.

I moved the function to a view model to keep things in order, but now I'm confused. First of all, the completion handler has to be called before the function returns, even though that seems exactly the opposite of what I want. I want to get the city and state values and then I want to save them through the handler. The second half of my confusion is in the calling of the function. If I assign a variable like cityState to receive the tuple from my method, the saving happens in a closure which I can't get to! (See Settings View code below.)

I could move everything back to the view where it was working just fine. But I'm trying to understand how MVVM is supposed to work.

Settings View Model

func getCityStateFromPostalCode(zip: String, completion: @escaping () -> ()) -> (String, String) {
   let geocoder = CLGeocoder()
   var city = ""
   var state = ""
        
   geocoder.geocodeAddressString(zip) { (placemarks, error) in
      if let placemark = placemarks?[0] {
         if placemark.postalCode == zip {
            city = placemark.locality!
            state = placemark.administrativeArea!
         }
      }
      completion()
   }
   return (city, state)
} 

SettingsView

let settingsVM = SettingsViewModel()

let cityState = settingsVM.getCityStateFromPostalCode(zip: companyZip) {
   let newCompany = Company(context: self.moc)
      newCompany.id = UUID()
      newCompany.city = ?? //can't use cityState here.
      newCompany.state = ?? // or here!
}

Upvotes: 5

Views: 3896

Answers (2)

Andrew
Andrew

Reputation: 11436

@ObservedObject var model: someViewModel

var body: some View {
    ScrollView {
        ScrollViewReader { pageScroller in

            ForEach(0..<100) { i in
                Text("Example \(i)")
                    .frame(width: 200, height: 200)
                    .background(colors[i % colors.count])
                    .id(i)
            }
            //Magic here
            .onReceive(model.scroller.objectWillChange) {
                pageScroller.scrollTo(model.scroller.scrollToPageName, anchor: .top)
            }
        }
    }
    .frame(height: 350)
}

class someViewModel: ObservableObject {
    //Magic here
    @ObservedObject var scroller: scrollerHelper

    init() {
        someDelayedCall()
    }

    func someDelayedCall(){
         scroller.scrollToPageName = 8
    }
}

//Magic here
class scrollerHelper: ObservableObject {
    @Published var scrollToPageName: Int = -1
}

main idea that scrollerHelper is must be inside of model as ObservedObject.

In this case you can use it as thing to force a code run from your view.

And magic here is that View will not be fully refreshed like in case of change of any @Published variable of model. This code will be run in the same way like code inside of press of the Button locaded in view! Awesome, right?)

But in case of you do not need such behavior you can use standard way of use swiftUI models. I have described it here:

https://stackoverflow.com/a/62919526/4423545

Upvotes: 1

pawello2222
pawello2222

Reputation: 54516

Return city and state in the completion handler, not in the return:

func getCityStateFromPostalCode(zip: String, completion: @escaping ((String, String)) -> ()) {
   let geocoder = CLGeocoder()
   var city = ""
   var state = ""
        
   geocoder.geocodeAddressString(zip) { (placemarks, error) in
      if let placemark = placemarks?[0] {
         if placemark.postalCode == zip {
            city = placemark.locality!
            state = placemark.administrativeArea!
         }
      }
      completion((city, state))
   }
} 

Then you can do:

settingsVM.getCityStateFromPostalCode(zip: companyZip) { city, state in
   let newCompany = Company(context: self.moc)
   newCompany.id = UUID()
   newCompany.city = city
   newCompany.state = state
}

Upvotes: 4

Related Questions