Search code examples
mvvmswiftuiappdelegateobservableobject

Passing Data between Delegate and ViewModel ObservableObjects


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
     }
}

Solution

  • 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 { ... }