I've created a class that implements CLLocationDelegate
and ObservableObject
so that it can let views subscribe to location updates using @StateObject
and @EnvironmentObject
. This works fine, but because only certain views need access to the location, I'd like to call startUpdatingLocation
and stopUpdatingLocation
when those views appear or disappear.
Assuming my view has access to the delegate and its location manager, where do I put the startUpdatingLocation
and stopUpdatingLocation
calls?
My first inclination was to use onAppear
and onDisappear
like so.
struct ViewThatNeedsLocation : View {
@EnvironmentObject var locationDelegate: MyLocationDelegate
var body: some View {
Text("")
.onAppear {
locationDelegate.manager.startUpdatingLocation()
}.onDisappear {
locationDelegate.manager.stopUpdatingLocation()
}
}
}
But this suffers from a couple problems:
onAppear
and onDisappear
can be called multiple times due to parent changes, even if the user never sees the view visibly change. onDisappear
is not guaranteed to be called before a subsequent onAppear
call.This means I can get the following order of events
onAppear
startUpdatingLocation (good)
onAppear (again)
startUpdatingLocation (redundant)
onDisappear (?)
stopUpdatingLocation (bad)
My guess is that ViewThatNeedsLocation
is being created and destroyed multiple times without the rendered content being changed. This would be fine if SwiftUI
provided some sort of guarantee about the order of the lifecycle modifiers, but it doesn't.
What is the correct way to do this? Is there a simple way to fire setup and teardown functions when a view appears and disappears reliably?
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
@Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
@Published var location: CLLocation?
@Published var region: CLRegion?
override init() {
super.init()
self.locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
extension LocationManager: CLLocationManagerDelegate {
func requestLocation() {
locationManager.requestLocation()
}
func updateLocation() {
locationManager.startUpdatingLocation()
}
func stopUpdatingLocation() {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.authorizationStatus = status
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
self.location = location
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error getting location \(error.localizedDescription)")
}
}
And in view struct:
@ObservedObject var locationManager = LocationManager()
@State private var location: CLLocation?
.onReceive(locationManager.$location, perform: userLocation)
.onReceive(locationManager.$authorizationStatus, perform: { status in
switch status {
case .authorizedAlways:
locationManager.updateLocation()
case .authorizedWhenInUse:
locationManager.updateLocation()
default:
break
}
})
I just need first location so i am calling locationManager.stopUpdatingLocation() in my view in userLocation function, but you can do that in onDisapear. here you have some demo
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.