Search code examples
swiftlocationcore-locationprivacy

iOS Swift 3: Location Privacy Denied


What if the location Privacy Access for an app is Denied?

Evening guys, I'm coding a simple App that uses location when in use.

The Design Pattern

  1. Before everything, when you launch the App, it will check if there is a permission already set.

    • If not, it shows an alert asking for permission.
    • If yes and granted, it proceeds doing its job.
    • If yes and denied, it shows an alert asking to grant access with a button pointing to settings.
      • it should be possible to return back from the settings to the app.
  2. The apps does its job.

  3. If the user change the privacy settings when the app is still open, the app should be notified, and repeat number 1.

The Code so Far

MainController

let manager = LocationManager()

override func viewDidLoad() {
        super.viewDidLoad()
        // ...

        if manager.getPermission() == false {
          //show alert
          showAcessDeniedAlert()
        }
        manager.onLocationFix = { 
          //This is a function used for a closure
        }

    }
}
func showAcessDeniedAlert() {
    let alertController = UIAlertController(title: "Location Accees Requested",
                                                message: "The location permission was not authorized. Please enable it in Settings to continue.",
                                                preferredStyle: .alert)

    let settingsAction = UIAlertAction(title: "Settings", style: .default) { (alertAction) in

        // THIS IS WHERE THE MAGIC HAPPENS!!!!
        if let appSettings = URL(string: UIApplicationOpenSettingsURLString) {
            UIApplication.shared.open(appSettings as URL)
        }
    }
    alertController.addAction(settingsAction)

    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
    alertController.addAction(cancelAction)

    present(alertController, animated: true, completion: nil)
}

LocationManager

import CoreLocation

extension Coordinate {
    init(location: CLLocation) {
        latitude = location.coordinate.latitude
        longitude = location.coordinate.longitude
    }
}

final class LocationManager: NSObject, CLLocationManagerDelegate {
    let manager = CLLocationManager()

    var onLocationFix: ((Coordinate) -> Void)?

    override init() {
        super.init()
        manager.delegate = self
        manager.requestLocation()
    }

  func getPermission() -> Bool {
      switch CLLocationManager.authorizationStatus() {
      case .authorizedAlways:
            return true
      case .authorizedWhenInUse:
            return true
      case .denied:
            return false
      case .restricted:
            return false
      case .notDetermined:
            manager.requestWhenInUseAuthorization()
            return getPermission()
      }
  }



    //MARK: CLLocationManagerDelegate
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if status == .authorizedWhenInUse {
            manager.requestLocation()
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.first else { return }

        let coordinate = Coordinate(location: location)
        if let onLocationFix = onLocationFix {
            onLocationFix(coordinate)
        }

    }
}

How Can I?

  1. How can I show The AlertController if the privacy is Denied?

    With this setup I'm having this error: Warning: Attempt to present <UIAlertController: 0x145ae7ee0> on <xxx.xxController: 0x143e04720> whose view is not in the window hierarchy!.

  2. How can I code the setting button pointing to Settings?

  3. How can I code: "from settings page, I could return to the app"?


Solution

    1. viewDidLoad is called after first access self.view property. This mean that for first is called viewDidLoad after this self.view is added on window hierarchy. Move checking code to viewDidAppear(_:) function and be sure that your view controller is presented.
    2. Code that open Settings app seems to be ok, but don't forget to check from which system version it's available.
    3. Your app is not able to interact somehow with other apps while it's in background state.