MacKuba

🍎 Kuba Suder's blog on Mac & iOS development

Accessing user location data in iOS 8

Categories: Cocoa, iPhone Comments: 1 comment

In iOS 8 Apple made some changes to how apps are supposed to request access to location data. If you use CLLocationManager and you don’t make any changes for iOS 8 compatibility, your app might stop receiving location data at all.

Permission types

The biggest change is that apps can now ask to get location data either “Always” or “While Using”:

The “Always” permission is used for the less precise but more battery-friendly location monitoring modes, like the “significant changes” API or region monitoring. “Always” means that you want access to the location data 24/7, so obviously you can’t use the standard API that returns precise GPS data every second, at least not all the time, or you’d drain any iPhone in a couple of hours.

Few apps should actually need this permission, and I think the main point of this change was to discourage apps from spying on you permanently unless they have a really good reason. One app for which this does make sense is Swarm/Foursquare, which wants to provide users relevant info while they move around the city. Also any apps that detect beacons will require this, or apps that execute some reminders or actions when you come to a different place (e.g. todo apps).

The “While Using” permission is what at least 90% of apps should use instead. “Using” means that you either only use the location data while the app is actually on the screen and the user is interacting with it, or the app is in the background, but it’s doing something important for the user for which the location data is crucial (think: navigation or some kind of fitness activity). In the latter case (e.g. RunKeeper running in the background) iOS shows an extremely conspicuous blue bar at the top of the screen to make it clear that the location data is being actively accessed:

RunKeeper is Using Your Location

If you only use location data while the user is interacting with the app in the foreground, then it’s recommended that you stop monitoring location immediately when the app starts hiding into the background. If you don’t do this quickly enough, the user will see this blue bar for a couple of seconds after closing your app (you can see this e.g. in the Google Maps app).

Note: it’s perfectly acceptable for apps to include both permission options – in fact I’d say that it should be considered a good practice for apps that request the “Always” permission to also fall back to “While Using”. Again, a good example is Foursquare. By default it asks for “Always”, so that it can show you notifications when you come near interesting places. However, if the user does not want to be tracked by Foursquare all the time, but they still want to be able to use location data in the app (in order to search for nice cafes or restaurants nearby or to check in to them), they can give Foursquare the “While Using” permission and only let it access their location while the app is active. A nice app should respect that decision and not present the user with an “all or nothing” choice.

At the moment most apps I use on my iPhone have been updated to only require a “While Using” permission, except those that are basically abandoned and haven’t been updated in the last few months. Also, both Foursquare apps give users a choice to allow access either always or while using (Foursquare added this earlier, Swarm only recently).

Requesting access

Here’s how you should request location data now:

1. Add description keys to Info.plist.

Add an NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription key to the Info.plist (or both if you want to allow both options). This should include a short explanation why the app needs location data, and will be displayed in the location permission popup.

This is absolutely essential – if you don’t add the required key, the methods requesting authorization will silently fail. You won’t even get a warning. So if you don’t get any popups, first make sure that the key is there and is named correctly (e.g. not “WhileInUse” instead of “WhenInUse”…).

These messages can be localized by adding a strings file named InfoPlist.strings and localizing it to any languages you need – simply include a line like this in the localized versions of the file:

"NSLocationWhenInUseUsageDescription" = "All your data are belong to us";

2. Check current authorization status.

First, call CLLocationManager.locationServicesEnabled(). If it returns false, then location access is disabled globally and there’s no point requesting it for your app.

If it’s true, call CLLocationManager.authorizationStatus(). This can return one of 5 results:

  • .NotDetermined – this means that we haven’t asked the user for permission yet, and that you should do that now
  • .AuthorizedWhenInUse – we have the “While Using” permission
  • .AuthorizedAlways – we have the “Always” permission (a location permission granted in iOS 7 also counts as “Always”)
  • .Denied – user has decided to not give the app any permissions to the location data
  • .Restricted – the location data cannot be accessed, and it’s something beyond the user’s control (e.g. because of parental controls)

3. Request the location access permission.

If they haven’t granted or refused the permission yet, create an instance of CLLocationManager, set its delegate property and call either requestWhenInUseAuthorization or requestAlwaysAuthorization. If you want to allow both options, just ask for “Always” and allow user to choose “While Using” manually in settings by including a second plist key.

If you don’t call this and go straight to startUpdatingLocation, it won’t return any location data; again, as with the missing plist keys – you won’t get any errors, it will just silently fail, leaving you wondering what’s wrong.

Note: remember to keep a reference to the manager somewhere – if the popup shows up but then disappears by itself, it’s probably because the manager got cleaned up by ARC.

If you want to support iOS 7 too, make sure you check if the request method is available:

if locationManager.respondsToSelector("requestWhenInUseAuthorization") {
  locationManager.requestWhenInUseAuthorization()
}

On iOS 7 the popup was triggered by simply starting location tracking, so if you skip this call it will work as before.

4. React to authorization callback.

When the user responds to the permission popup, you get a callback to locationManager:didChangeAuthorizationStatus:. Check the returned status and react accordingly (either start monitoring location or give up).

Displaying location on a map view

One thing commonly used together with Core Location is a MapKit map view. In the Interface Builder, you can configure it to show location by checking the “Shows: User Location” checkbox in the map view attributes section.

One problem: the map loads and starts showing location right after the XIB is loaded, before you have a chance to check the permission. This makes iOS print this warning in the console:

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

Even if you set up everything correctly as described above, and request authorization immediately in viewDidLoad, it will show the popup first, but then it will print the warning anyway, even if the user clicks “Allow”.

So assuming that getting this warning is a bad thing (I have no idea if ignoring it has any consequences), that makes this checkbox basically useless at the moment, as far as I understand. What you should do instead is disable showing user location in the XIB, check the permission in viewDidLoad, and only then once you have the permission enable that feature in code by setting the map’s showsUserLocation to true (and possibly also the userTrackingMode property).

1 comment:

Greg

Hi! For those involved in iOS localization projects, I recommend trying a software localization tool like https://poeditor.com/. Its work interface is flexible and simple to use, there are plenty of features (API, tagging system, automatic translation, GitHub integration, WordPress plugin), making your localization workflow easier.

Leave a comment

*

*
This will only be used to display your Gravatar image.

*

What's the name of the base class of all AppKit and UIKit classes?

*