3
votes

In my app I use a MKMapKit and MKUserTrackingBarButtonItem to locate user on tap. When I tap to this button, output console returns this error:

Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first.

According to me, this error is caused because the requestWhenInUseAuthorization() is not invoked yet. In fact, MKUserTrackingBarButtonItem tap calls the func mapViewWillStartLocatingUser that presupposes the CLLocationManager requestWhenInUseAuthorization first:

 func mapViewWillStartLocatingUser(mapView: MKMapView!) {
    println("**** mapViewWillStartLocatingUser ****")

    // i servizi di localizzazione sono abilitati?
    if (CLLocationManager.locationServicesEnabled())
    {
        // setto il locationManager ed il delegate
        locationManager = CLLocationManager()
        locationManager.delegate = self

        // abbiamo l'autorizzazione ad accedere ai servizi di localizzazione?
        switch CLLocationManager.authorizationStatus(){
        case .Denied:
            // no
            displayAlertToEnableLocationServicesApp()
            //displayAlertWithTitle("Denied", message: "Location services are not allowed for this app")
        case .Restricted:
            // no
            displayAlertToEnableLocationServicesApp()
        case .NotDetermined:
            // bisogna chiedere all'utente
            println("Not Determined")
            if (locationManager != nil)
            {
                locationManager.requestWhenInUseAuthorization()
            }
        default:
            // si
            println("Authorized")
            if (locationManager != nil)
            {
                locationManager.desiredAccuracy = kCLLocationAccuracyBest
                locationManager.startUpdatingLocation()
            }
        }

    }
    else
    {
        println("Location services are not enabled")

        displayAlertWithTitle("Location Services are Turned Off", message: "Please open settings and turn on location services")
    }
}

// funzione della mappa
func mapView(mapView: MKMapView!, didFailToLocateUserWithError error: NSError!) {
    println("**** didFailToLocateUserWithError **** ", error)
}

// funzione della mappa
func mapView(mapView: MKMapView!, didChangeUserTrackingMode mode: MKUserTrackingMode, animated: Bool) {
    println("**** didChangeUserTrackingMode ****")
}

// funzione del CoreLocation che setta la visuale in base alla localizzaizone dell'utente
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
    println("**** didUpdateLocations ****")

    //self.mapView.showsUserLocation = true


    // aggiorno le coordinate dell'utente
    posizioneUtente = (locations[0] as CLLocation).coordinate
    //posizioneUtente = manager.location.coordinate
    println("Posizione utente aggiornata (lat: \(posizioneUtente.latitude) long: \(posizioneUtente.longitude))")

    // setto la camera sulla posizione dell'utente
    var camera = MKMapCamera(lookingAtCenterCoordinate: posizioneUtente, fromEyeCoordinate: posizioneUtente, eyeAltitude: 500)
    // utilizzo un'animazione piĆ¹ lenta
    UIView.animateWithDuration(1.8, animations:{
        self.mapView.camera = camera
    })

    locationManager.stopUpdatingLocation()

    // cambio l'icona del bottone della localizzazione
    //locationOutlet.setImage(UIImage(named: "LocalizzazioneEmpty"), forState: UIControlState.Normal)

}



// funzione del CoreLocation
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!)
{
    println("**** didFailWithError ****")
    println("Error: \(error.localizedDescription)")
}

// funzione del CoreLocation
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
    print("The authorization status of location " + "services is changed to: ")

    switch CLLocationManager.authorizationStatus(){
    case .Denied:
        println("Denied")
    case .NotDetermined:
        println("Not determined")
    case .Restricted:
        println("Restricted")
    default:
        println("Authorized")
    }
}

I added naturally the Info.plist key: NSLocationWhenInUseUsageDescription .

My question is: How can I do to call the CLLocationManager requestWhenInUseAuthorization on MKUserTrackingBarButtonItem tap but before the launch of mapViewWillStartLocatingUser. I want that the user should get the prompt when tap the button and no in viewDidLoad

Thanks Sorry for my English

2

2 Answers

2
votes

When you invoke requestWhenInUseAuthorization the system prompts the user for permission, when that permission is not authorized yet. Once the user gave that permission, the question will not appear again.

To react on the user's response you have to implement locationManager(_:didChangeAuthorizationStatus:) from the CLLocationManagerDelegate protocol and only start startUpdateLocations() if the user gave the permission.

1
votes

I struggled with this as well. In the end, I moved all references to locationManager to a single method that only invoked methods in the CLLocationManager when the authorizationStatus was in a state that would allow it:

private func enableLocationServices(enabled: Bool) {
    switch CLLocationManager.authorizationStatus() {
    case .AuthorizedAlways: fallthrough
    case .AuthorizedWhenInUse:
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        if enabled {
            locationManager.startUpdatingLocation()
        }
        else {
            locationManager.stopUpdatingLocation()
        }
    case  .NotDetermined: break
    case .Denied: break
    case .Restricted: break
    }
}

After doing this, I was still getting the 'Trying to start MapKit location updates without prompting for location authorization' warning. It turns out the line that was still causing me headaches was:

        mapView.showsUserLocation = true

It wasn't until I moved this line from my viewDidLoad to the switch statement starting location services that the warnings went away. It wasn't obvious to me that this line would cause problems because it wasn't part of the CLLocationManager class; but it does make sense after thinking about it. The map view is using the location services to put the blinking blue dot on the screen. The final working solution (for me) looks like:

let locationManager = CLLocationManager()

// MARK: Viewcontroller lifecyle

override func viewDidLoad() {
    super.viewDidLoad()

    locationManager.requestWhenInUseAuthorization()
    locationManager.delegate = self
    enableLocationServices(true)
.
.
}

.
.
// MARK: CLLocationManager Delegate Methods

func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
    switch status {
    case .AuthorizedAlways: fallthrough
    case .AuthorizedWhenInUse:
        enableLocationServices(true)
    default:
        break
    }
}

private func enableLocationServices(enabled: Bool) {
    switch CLLocationManager.authorizationStatus() {
    case .AuthorizedAlways: fallthrough
    case .AuthorizedWhenInUse:
        mapView.showsUserLocation = true
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        if enabled {
            locationManager.startUpdatingLocation()
        }
        else {
            locationManager.stopUpdatingLocation()
        }
    case  .NotDetermined: break
    case .Denied: break
    case .Restricted: break
    }
}