iosswiftobjective-ccllocationmanagercllocation

How should CLLocationManager status be checked in locationManagerDidChangeAuthorization before loading an iOS view?


Since iOS 16/Xcode 14, I get this error:

This method can cause UI unresponsiveness if invoked on the main thread. Instead, consider waiting for the -locationManagerDidChangeAuthorization: callback and checking authorizationStatus first."?

I am observing scrolling freezes and long press freezes.

How should what Apple is suggesting be done?

This is my current code segment

     /In ViewDidLoad
      if CLLocationManager.locationServicesEnabled() {
        let authorizationStatus: CLAuthorizationStatus
        if #available(iOS 14, *) {
            authorizationStatus = locationManager.authorizationStatus
        } else {
            authorizationStatus = CLLocationManager.authorizationStatus()
        }

        switch authorizationStatus {
        case .authorizedAlways, .authorizedWhenInUse:
           locationManager.delegate = self                
           locationManager.distanceFilter = kCLDistanceFilterNone
           locationManager.startUpdatingLocation()               
           self.locationManager.requestAlwaysAuthorization()
           self.locationManager.requestWhenInUseAuthorization()
           self.locationManager.allowsBackgroundLocationUpdates = true
           //////here data loading happens too////////////
        case .notDetermined:
        case .restricted:
        case .denied:
        @unknown default:
            print("Location services are not enabled")
     }

    /outside ViewDidLoad
     func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])   { 
     ///location database related stuff
     }

I tried async/await as suggested here, but it didn't fix the issue. https://developer.apple.com/forums/thread/714467


Solution

  • LocationManager's authorizationStatus is designed be used when CLLocationManager delegate indicates that there has been a change in your apps authorisation to use location services.

    The system calls the CLLocationManager delegate method locationManagerDidChangeAuthorization(_ :) when the app’s authorisation status changes. This could be for user-initiated changes or when the user changes permissions after you request requestWhenInUseAuthorization() or requestAlwaysAuthorization(). It will also call the delegate method whenever you create a new CLLocationManager instance, so it will prompt you to check the value when first run.

    When the delegate method is called you should get the value from the authorizationStatus property and store it. If the status is then changed (manually or in response to system usage prompts) it will be called again and you should update your stored value; if not the value you have saved remains valid and there is no need to request it from your viewDidLoad.

    EDIT to address comment...

    You shouldn't be running the location code in viewDidLoad - this should be initiated from the delegate when you get an updated location. In viewDidLoad initiate the locations services, eg. by calling this :

    //struct/class-level properties
    var locationManager: CLLocationManager
    var authStatus = CLAuthorizationStatus.notDetermined
    
    func startLocationServices() {
       locationManager = CLLocationManager()
       locationManager.delegate = self
       if authStatus == .notDetermined {
          locationManager.requestWhenInUseAuthorization()
       }
       locationManager.startUpdatingLocation()
       }
    

    Then action location information with the CLLocationManagerDelegate

    extension HomeVC: CLLocationManagerDelegate {
       func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
          authStatus = manager.authorizationStatus
          // you could use a `didSet` on `authStatus` to react to changes
       }
       
       func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
          if let newLocation = locations.first {
             process(newLocation)
          }
       }
    }
    

    and then in the process(_:) do what you were trying to do in viewDidLoad