Just getting started with SwiftUI.
I have a GoogleMapsView in a ContentView
using the CLLocationManager
I capture events in the AppDelegate
or SceneDelegate
class by means of extending them with CLLocationManagerDelegate
.
How can I invoke a method in the GoogleMapsView
from the AppDelegate
or SceneDelegate
?
In this instance I want to call the .animate
method when the location change event is sent to the AppDelegate
instance via the CLLocationManagerDelegate
, but the question is really more generic.
I made and implementation of CLLocationManager and MKMapView and it is almost the same as maps, hope it will help you:
Short answer: declaring a @Binding var foo: Any
you will be able to make changes inside GoogleMapView every time that foo changes, in this case foo is your location, so you can call animate every time foo is updated.
Long answer:
First I created a Mapview that conforms UIViewRepresentable protocol, just as you did, but adding a @Binding variable, this is my "trigger".
MapView:
struct MapView: UIViewRepresentable {
@Binding var location: CLLocation // Create a @Binding variable that keeps the location where I want to place the view, every time it changes updateUIView will be called
private let zoomMeters = 400
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
let mapView = MKMapView(frame: UIScreen.main.bounds)
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
//When location changes, updateUIView is called, so here I move the map:
let region = MKCoordinateRegion(center: location.coordinate,
latitudinalMeters: CLLocationDistance(exactly: zoomMeters)!,
longitudinalMeters: CLLocationDistance(exactly: zoomMeters)!)
mapView.setRegion(mapView.regionThatFits(region), animated: true)
}
}
Then I placed my MapView in my ContentView, passing a location argument, which I will explain next:
ContentView:
struct ContentView: View {
@ObservedObject var viewModel: ContentViewModel
var body: some View {
VStack {
MapView(location: self.$viewModel.location)
}
}
}
In my ViewModel, I handle location changes using a delegate, here is the code with more details in comments:
class ContentViewModel: ObservableObject {
//location is a Published value, so the view is updated every time location changes
@Published var location: CLLocation = CLLocation.init()
//LocationWorker will take care of CLLocationManager...
let locationWorker: LocationWorker = LocationWorker()
init() {
locationWorker.delegate = self
}
}
extension ContentViewModel: LocationWorkerDelegate {
func locationChanged(lastLocation: CLLocation?) {
//Location changed, I change the value of self.location, it is a @Published value so it will refresh the @Binding variable inside MapView and call MapView.updateUIView
self.location = CLLocation.init(latitude: lastLocation!.coordinate.latitude, longitude: lastLocation!.coordinate.latitude)
}
}
And finally here is LocationWorker which take cares of CLLocationManager():
class LocationWorker: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
var delegate: LocationWorkerDelegate?
let objectWillChange = PassthroughSubject<Void, Never>()
@Published var locationStatus: CLAuthorizationStatus? {
willSet {
objectWillChange.send()
}
}
@Published var lastLocation: CLLocation? {
willSet {
objectWillChange.send()
}
}
override init() {
super.init()
self.locationManager.delegate = self
//...
}
}
protocol LocationWorkerDelegate {
func locationChanged(lastLocation: CLLocation?)
}
extension LocationWorker: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
self.lastLocation = location
//When location changes: I use my delegate ->
if delegate != nil {
delegate!.locationChanged(lastLocation: lastLocation)
}
}
}