I have an app built using SwiftUI that works with user location. Using online tutorials, I have come up with a class LocationManager
that handles the request using the delegate method and has an attribute @Published
that sets the location as soon as it receives it.
I also have a ViewModel
that has a function getData(location: CLLocation)
that will properly update my view after an async call to a different API.
My question is, what is the best way to connect the LocationManager
with the ViewModel
, so that as soon as the LocationManager
gets the location using the delegate it automatically calls the getData()
function with that value?
I've tried to come up with a few solutions on my own, such as passing a closure to the LocationManager
to call viewModel.getData()
when the delegate is updated, but I got an issue with the "closure capturing a mutating self parameter". Any help would be greatly appreciated!!
Here is the code in question:
final class LocationManager: NSObject, ObservableObject {
@Published var location: CLLocation?
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
DispatchQueue.main.async {
self.location = location
}
}
}
class ViewModel: ObservableObject {
@Published dataArray = [Model]()
func getData(location: CLLocation) {
// async api call
// update dataArray for view in completion handler
}
}
struct ShowData: View {
// initialize LocationManager
@StateObject var locationManager = LocationManager()
// initialize ViewModel
@StateObject var viewModel = ViewModel()
var body: some View {
// show dataArray
}
}
You can own the LocationManager in your view model:
class ViewModel: ObservableObject {
@Published dataArray = [Model]()
var lm = LocationManager()
}
Then, you could architect the LocationManager to take a separate delegate (which could be the view model), or, you could use Combine to listen for changes on the @Published
property on the LocationManager
:
cancellable = lm.$location.sink { ... }