3

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

Giuseppe
  • 165
  • 2
  • 10

2 Answers2

2

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.

zisoft
  • 22,770
  • 10
  • 62
  • 73
  • So I should ignore these errors that appears on output console? My problem is where and when should I call requestWhenInUseAuthorizatio(). Because if I call it on button tap, it shows errors – Giuseppe Apr 01 '15 at 18:33
  • The error message indicates that you called `startUpdateLocation()` without asking for permission first. The authorization status is not `Authorized`. You have to make sure that `requestWhenInUseAuthorization` is called first and `startUpdateLocation()` may only be called when the user's response is positive. – zisoft Apr 01 '15 at 18:36
  • No, the errors message indicate that when I click on button, the delegate mapViewWillStartLocatingUser starts, but without authorization. When I launch app for first time, authorization is NotDetermined and return this error. Is here my problem, only at the first launch – Giuseppe Apr 01 '15 at 18:39
  • Do I call necessarily requestWhenInUseAuthorization in ViewDidLoad()? – Giuseppe Apr 02 '15 at 20:24
  • It must be called before `startUpdateLocation()`. The prompt appears only once. – zisoft Apr 02 '15 at 20:57
  • What you said works perfectly if I use a button to locate. Instead if I use MKUserTrackingBarButtonItem it doesn't work maybe because before tap on it is necessary call the request. I tried also to call the request in mapWillStartLocatingUser(), but nothing... – Giuseppe Apr 02 '15 at 21:05
  • Its not promoting for permission for me? – SleepsOnNewspapers Apr 03 '15 at 18:54
  • Also you need to allocate class by adding locationManager = [[CLLocationManager alloc]init]; – Gajendra K Chauhan May 18 '15 at 07:05
  • This is just what I needed. Because I was getting the location popup to show but my app would still use the location data anyway lol... – Supertecnoboff Sep 10 '15 at 09:41
1

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
    }
}