Reputation: 323
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.
Upvotes: 4
Views: 15574
Reputation: 3340
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)
}
}
}
Upvotes: 6
Reputation: 119108
Instead of calling a View
method directly from outside, you should revise your logic a bit and just change some kind of a state
somewhere and let the View
update itself. Take a look at this algorithm:
view
(You should pass a reference to that view
all the way up to the app delegate)Although the above algorithm is what you are looking for originally, It isn't the best way and I don't recommend it at all! But it will work 🤷🏻♂️
State
somewhere. (maybe an ObservedObject
variable inside itself or an EnvironmentObject
or etc.)This is how it should be done. But there are more than just one way to implement this and you should consider your preferences to pick the best for you.
Upvotes: 0