Reputation: 1945
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
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
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