615
votes

My app that worked fine on iOS 7 doesn't work with the iOS 8 SDK.

CLLocationManager doesn't return a location, and I don't see my app under Settings -> Location Services either. I did a Google search on the issue, but nothing came up. What could be wrong?

26
You can also use this as reference app for the solution github.com/jbanford/ConstantLocationUpdatesashok vadivelu
I posted about some of the changes to the location manager in iOS 8 here: nevan.net/2014/09/core-location-manager-changes-in-ios-8nevan king
You could try using this library which simplifies the Core Location APIs and exposes a nice blocks based interface, and normalises all the differences between different iOS versions (Full disclosure: I'm the author): github.com/lmirosevic/GBLocationlms
I found some reference here datacalculation.blogspot.in/2014/11/…iOS Test
@nevanking You sir! need a crown! I have been fighting with this problem since change and have yet to find an "guide" how to fix it, that was noob friendly.. Your guide made an idiot like me, handle the problem my self. Thank you so much for that link.Patrick R

26 Answers

1093
votes

I ended up solving my own problem.

Apparently in iOS 8 SDK, requestAlwaysAuthorization (for background location) or requestWhenInUseAuthorization (location only when foreground) call on CLLocationManager is needed before starting location updates.

There also needs to be NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription key in Info.plist with a message to be displayed in the prompt. Adding these solved my problem.

enter image description here

For more extensive information, have a look at: Core-Location-Manager-Changes-in-ios-8

317
votes

I was pulling my hair out with the same problem. Xcode gives you the error:

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

But even if you implement one of the above methods, it won't prompt the user unless there is an entry in the info.plist for NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription.

Add the following lines to your info.plist where the string values represent the reason you you need to access the users location

<key>NSLocationWhenInUseUsageDescription</key>
<string>This application requires location services to work</string>

<key>NSLocationAlwaysUsageDescription</key>
<string>This application requires location services to work</string>

I think these entries may have been missing since I started this project in Xcode 5. I'm guessing Xcode 6 might add default entries for these keys but have not confirmed.

You can find more information on these two Settings here

105
votes

To ensure that this is backwards compatible with iOS 7, you should check whether the user is running iOS 8 or iOS 7. For example:

#define IS_OS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)

//In ViewDidLoad
if(IS_OS_8_OR_LATER) {
   [self.locationManager requestAlwaysAuthorization];
}

[self.locationManager startUpdatingLocation];
51
votes
- (void)startLocationManager
{
    locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;
    locationManager.distanceFilter = kCLDistanceFilterNone; //whenever we move
    locationManager.desiredAccuracy = kCLLocationAccuracyBest;

    [locationManager startUpdatingLocation];
    [locationManager requestWhenInUseAuthorization]; // Add This Line


}

And to your info.plist File enter image description here

30
votes

According to the Apple docs:

As of iOS 8, the presence of a NSLocationWhenInUseUsageDescription or a NSLocationAlwaysUsageDescription key value in your app's Info.plist file is required. It's then also necessary to request permission from the user prior to registering for location updates, either by calling [self.myLocationManager requestWhenInUseAuthorization] or [self.myLocationManager requestAlwaysAuthorization] depending on your need. The string you entered into the Info.plist will then be displayed in the ensuing dialog.

If the user grants permission, it's business as usual. If they deny permission, then the delegate is not informed of location updates.

28
votes
- (void)viewDidLoad
{
    
    [super viewDidLoad];
    self.locationManager = [[CLLocationManager alloc] init];
    
    self.locationManager.delegate = self;
    if([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]){
        NSUInteger code = [CLLocationManager authorizationStatus];
        if (code == kCLAuthorizationStatusNotDetermined && ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)] || [self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)])) {
            // choose one request according to your business.
            if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]){
                [self.locationManager requestAlwaysAuthorization];
            } else if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) {
                [self.locationManager  requestWhenInUseAuthorization];
            } else {
                NSLog(@"Info.plist does not contain NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription");
            }
        }
    }
    [self.locationManager startUpdatingLocation];
}

>  #pragma mark - CLLocationManagerDelegate

    - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
    {
        NSLog(@"didFailWithError: %@", error);
        UIAlertView *errorAlert = [[UIAlertView alloc]
                                   initWithTitle:@"Error" message:@"Failed to Get Your Location" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [errorAlert show];
    }
    
    - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
    {
        NSLog(@"didUpdateToLocation: %@", newLocation);
        CLLocation *currentLocation = newLocation;
        
        if (currentLocation != nil) {
            longitudeLabel.text = [NSString stringWithFormat:@"%.8f", currentLocation.coordinate.longitude];
            latitudeLabel.text = [NSString stringWithFormat:@"%.8f", currentLocation.coordinate.latitude];
        }
    }

In iOS 8 you need to do two extra things to get location working: Add a key to your Info.plist and request authorization from the location manager asking it to start. There are two Info.plist keys for the new location authorization. One or both of these keys is required. If neither of the keys are there, you can call startUpdatingLocation but the location manager won’t actually start. It won’t send a failure message to the delegate either (since it never started, it can’t fail). It will also fail if you add one or both of the keys but forget to explicitly request authorization. So the first thing you need to do is to add one or both of the following keys to your Info.plist file:

  • NSLocationWhenInUseUsageDescription
  • NSLocationAlwaysUsageDescription

Both of these keys take a string

which is a description of why you need location services. You can enter a string like “Location is required to find out where you are” which, as in iOS 7, can be localized in the InfoPlist.strings file.

enter image description here

19
votes

My solution which can be compiled in Xcode 5:

#ifdef __IPHONE_8_0
    NSUInteger code = [CLLocationManager authorizationStatus];
    if (code == kCLAuthorizationStatusNotDetermined && ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)] || [self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)])) {
        // choose one request according to your business.
        if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]){
            [self.locationManager requestAlwaysAuthorization];
        } else if([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) {
            [self.locationManager  requestWhenInUseAuthorization];
        } else {
            NSLog(@"Info.plist does not contain NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription");
        }
    }
#endif
    [self.locationManager startUpdatingLocation];
17
votes

The old code for asking location won't work in iOS 8. You can try this method for location authorization:

- (void)requestAlwaysAuthorization
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];

    // If the status is denied or only granted for when in use, display an alert
    if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status ==        kCLAuthorizationStatusDenied) {
        NSString *title;
        title = (status == kCLAuthorizationStatusDenied) ? @"Location services are off" :   @"Background location is not enabled";
        NSString *message = @"To use background location you must turn on 'Always' in the Location Services Settings";

        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
                                                            message:message
                                                           delegate:self
                                                  cancelButtonTitle:@"Cancel"
                                                  otherButtonTitles:@"Settings", nil];
        [alertView show];
    }
    // The user has not enabled any location services. Request background authorization.
    else if (status == kCLAuthorizationStatusNotDetermined) {
        [self.locationManager requestAlwaysAuthorization];
    }
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1) {
        // Send the user to the Settings for this app
        NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
        [[UIApplication sharedApplication] openURL:settingsURL];
    }
}
13
votes

In iOS 8 you need to do two extra things to get location working: Add a key to your Info.plist and request authorization from the location manager asking it to start

info.plist:

<key>NSLocationUsageDescription</key>
<string>I need location</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>I need location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>I need location</string>

Add this to your code

if (IS_OS_8_OR_LATER)
{
    [locationmanager requestWhenInUseAuthorization];

    [locationmanager requestAlwaysAuthorization];
}
11
votes

One common error for Swift developers:

First make sure you add a value to the plist for either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription.

If you are still not seeing a window pop up asking for authorization, look to see if you are putting the line var locationManager = CLLocationManager() in your View Controller's viewDidLoad method. If you do, then even if you call locationManager.requestWhenInUseAuthorization(), nothing will show up. This is because after viewDidLoad executes, the locationManager variable is deallocated (cleared out).

The solution is to locate the line var locationManager = CLLocationManager() at the top of the class method.

9
votes

Before [locationManager startUpdatingLocation];, add an iOS8 location services request:

if([locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
    [locationManager requestAlwaysAuthorization];

Edit your app's Info.plist and add key NSLocationAlwaysUsageDescription with the string value that will be displayed to the user (for example, We do our best to preserve your battery life.)

If your app needs location services only while the app is open, replace:

requestAlwaysAuthorization with requestWhenInUseAuthorization and

NSLocationAlwaysUsageDescription with NSLocationWhenInUseUsageDescription.

9
votes

I was working on an app that was upgraded to iOS 8 and location services stopped working. You'll probably get and error in the Debug area like so:

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

I did the least intrusive procedure. First add NSLocationAlwaysUsageDescription entry to your info.plist:

Enter image description here

Notice I didn't fill out the value for this key. This still works, and I'm not concerned because this is a in house app. Also, there is already a title asking to use location services, so I didn't want to do anything redundant.

Next I created a conditional for iOS 8:

if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
    [_locationManager requestAlwaysAuthorization];
}

After this the locationManager:didChangeAuthorizationStatus: method is call:

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:  (CLAuthorizationStatus)status
{
    [self gotoCurrenLocation];
}

And now everything works fine. As always, check out the documentation.

7
votes

Solution with backward compatibility:

SEL requestSelector = NSSelectorFromString(@"requestWhenInUseAuthorization");
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined &&
    [self.locationManager respondsToSelector:requestSelector]) {
    [self.locationManager performSelector:requestSelector withObject:NULL];
} else {
    [self.locationManager startUpdatingLocation];
}

Setup NSLocationWhenInUseUsageDescription key in your Info.plist

6
votes

Solution with backward compatibility which doesn't produce Xcode warnings:

SEL requestSelector = NSSelectorFromString(@"requestWhenInUseAuthorization");
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined &&
  [self.locationManager respondsToSelector:requestSelector]) {
((void (*)(id, SEL))[self.locationManager methodForSelector:requestSelector])(self.locationManager, requestSelector);
  [self.locationManager startUpdatingLocation];
} else {
  [self.locationManager startUpdatingLocation];
}

Setup NSLocationWhenInUseUsageDescription key in your Info.plist.

For iOS version 11.0+ : Setup NSLocationAlwaysAndWhenInUseUsageDescription key in your Info.plist. along with other 2 keys.

5
votes

This is issue with ios 8 Add this to your code

if (IS_OS_8_OR_LATER)
{
    [locationmanager requestWhenInUseAuthorization];

    [locationmanager requestAlwaysAuthorization];
}

and to info.plist:

 <key>NSLocationUsageDescription</key>
 <string>I need location</string>
 <key>NSLocationAlwaysUsageDescription</key>
 <string>I need location</string>
 <key>NSLocationWhenInUseUsageDescription</key>
 <string>I need location</string>
4
votes

To Access User Location in iOS 8 you will have to add,

NSLocationAlwaysUsageDescription in the Info.plist 

This will ask the user for the permission to get their current location.

4
votes

Objective-C Procedure Follow the below instructions:

For iOS-11 For iOS 11 have a look at this Answer: iOS 11 location access

Need to Add two Keys into plist and provide message as below image:

 1. NSLocationAlwaysAndWhenInUseUsageDescription 
 2. NSLocationWhenInUseUsageDescription
 3. NSLocationAlwaysUsageDescription

enter image description here For iOS-10 and below:

NSLocationWhenInUseUsageDescription

enter image description here

locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
if([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]){
    [locationManager requestWhenInUseAuthorization];
}else{
    [locationManager startUpdatingLocation];
} 

Delegate Methods

#pragma mark - Lolcation Update 
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    NSLog(@"didFailWithError: %@", error);
    UIAlertView *errorAlert = [[UIAlertView alloc]
                               initWithTitle:@"Error" message:@"Failed to Get Your Location" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [errorAlert show];
}
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
    switch (status) {
        case kCLAuthorizationStatusNotDetermined:
        case kCLAuthorizationStatusRestricted:
        case kCLAuthorizationStatusDenied:
        {
            // do some error handling
        }
            break;
        default:{
            [locationManager startUpdatingLocation];
        }
            break;
    }
}
- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray *)locations
{
    CLLocation *location = [locations lastObject];
    userLatitude =  [NSString stringWithFormat:@"%f", location.coordinate.latitude] ;
    userLongitude =  [NSString stringWithFormat:@"%f",location.coordinate.longitude];
    [locationManager stopUpdatingLocation];
}

Swift Procedure

Follow the below instructions:

For iOS-11 For iOS 11 have a look at this Answer: iOS 11 location access

Need to Add two Keys into plist and provide message as below image:

 1. NSLocationAlwaysAndWhenInUseUsageDescription 
 2. NSLocationWhenInUseUsageDescription
 3. NSLocationAlwaysUsageDescription

enter image description here For iOS-10 and below:

enter image description here

import CoreLocation
class ViewController: UIViewController ,CLLocationManagerDelegate {
var locationManager = CLLocationManager()

//MARK- Update Location 
func updateMyLocation(){
    locationManager.delegate = self;
    locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
    if locationManager.respondsToSelector(#selector(CLLocationManager.requestWhenInUseAuthorization)){
       locationManager.requestWhenInUseAuthorization()
    }
    else{
        locationManager.startUpdatingLocation()
    }
}

Delegate Methods

//MARK: Location Update
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
    NSLog("Error to update location :%@",error)
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
    switch status {
    case .NotDetermined: break
    case .Restricted: break
    case .Denied:
            NSLog("do some error handling")
        break
    default:
        locationManager.startUpdatingLocation()
    }
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
     let location = locations.last! as CLLocation
    var latitude = location.coordinate.latitude
    var longitude = location.coordinate.longitude

}
3
votes

For those using Xamarin, I had to add the key NSLocationWhenInUseUsageDescription to the info.plist manually since it was not available in the dropdowns in either Xamarin 5.5.3 Build 6 or XCode 6.1 - only NSLocationUsageDescription was in the list, and that caused the CLLocationManager to continue to fail silently.

2
votes
        // ** Don't forget to add NSLocationWhenInUseUsageDescription in MyApp-Info.plist and give it a string

        self.locationManager = [[CLLocationManager alloc] init];
        self.locationManager.delegate = self;
        // Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
        if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
            [self.locationManager requestWhenInUseAuthorization];
        }
        [self.locationManager startUpdatingLocation];


    // Location Manager Delegate Methods    
    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
    {
        NSLog(@"%@", [locations lastObject]);

}
2
votes

A little helper for all of you that have more than one Info.plist file...

find . -name Info.plist | xargs -I {} /usr/libexec/PlistBuddy -c 'Add NSLocationWhenInUseUsageDescription string' {} 

It will add the needed tag to all of the Info.plist files in the current directory (and subfolders).

Another is:

find . -name Info.plist | xargs -I {} /usr/libexec/PlistBuddy -c 'Set NSLocationWhenInUseUsageDescription $YOURDESCRIPTION' {} 

It will add your description to all files.

2
votes

I get a similar error in iOS9 (working with Xcode 7 and Swift 2): Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first. I was following a tutorial but the tutor was using iOS8 and Swift 1.2. There are some changes in Xcode 7 and Swift 2, I found this code and it works fine for me (if somebody needs help):

import UIKit
import MapKit
import CoreLocation

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {

    // MARK: Properties
    @IBOutlet weak var mapView: MKMapView!

    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.requestWhenInUseAuthorization()
        self.locationManager.startUpdatingLocation()
        self.mapView.showsUserLocation = true

    }

    // MARK: - Location Delegate Methods

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location = locations.last
        let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
        let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
        self.mapView.setRegion(region, animated: true)
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("Errors: " + error.localizedDescription)
    }
}

Finally, I put that in info.plist: Information Property List: NSLocationWhenInUseUsageDescription Value: App needs location servers for staff

2
votes

In order to access the users location in iOS. You need to add two keys

NSLocationWhenInUseUsageDescription

NSLocationAlwaysUsageDescription

into the Info.plist file.

    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Because I want to know where you are!</string>
    <key>NSLocationAlwaysUsageDescription</key>
    <string>Want to know where you are!</string>

See this below image.

Info.plist image

1
votes
  1. Add key NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription (background GPS use) with string asking to use GPS on each info.plist from each target.

  2. Ask for permission by running:

    [self initLocationManager:locationManager];

Where initLocationManager is:

// asks for GPS authorization on iOS 8
-(void) initLocationManager:(CLLocationManager *) locationManager{

    locationManager = [[CLLocationManager alloc]init];

    if([locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        [locationManager requestAlwaysAuthorization];
}

Remember that if the keys are not on each info.plist for each target the app will not ask the user. The if provides compatibility with iOS 7 and the respondsToSelector: method guarantees future compatibility rather than just solving the issue for iOS 7 and 8.

0
votes

The problem for me was that the class that was the CLLocationManagerDelegate was private, which prevented all the delegate methods from being called. Guess it's not a very common situation but thought I'd mention it in case t helps anyone.

0
votes

I add those key in InfoPlist.strings in iOS 8.4, iPad mini 2. It works too. I don't set any key, like NSLocationWhenInUseUsageDescription, in my Info.plist.


InfoPlist.strings:

"NSLocationWhenInUseUsageDescription" = "I need GPS information....";

Base on this thread, it said, as in iOS 7, can be localized in the InfoPlist.strings. In my test, those keys can be configured directly in the file InfoPlist.strings.

So the first thing you need to do is to add one or both of the > following keys to your Info.plist file:

  • NSLocationWhenInUseUsageDescription
  • NSLocationAlwaysUsageDescription

Both of these keys take a string which is a description of why you need location services. You can enter a string like “Location is required to find out where you are” which, as in iOS 7, can be localized in the InfoPlist.strings file.


UPDATE:

I think @IOS's method is better. Add key to Info.plist with empty value and add localized strings to InfoPlist.strings.