Kuba Suder's blog on Mac & iOS development

Apart from a whole bunch of new frameworks (see the whole list here), iOS 11 also makes some major changes to existing APIs. One of the affected areas is location tracking. If your app only uses location while the app is in the foreground, as most apps do, you might not have to change anything at all; however, if it’s one of those apps that continuously track user’s location throughout the day, you should probably book some time this summer for making some changes in how you do the tracking and testing possible usage scenarios.

How it worked in iOS 10

First, a bit of introduction for those not familiar with location tracking APIs – feel free to skip this section if you want.

In iOS 10 and earlier, apps can request access to user’s location in two ways, depending on how they plan to access and use it:

“While Using” access

Most apps only need to access user’s location while the user has the app in the foreground and is actively using it. This could be continuous access for displaying user’s position on a map, which is being updated as they move (MKMapView.showsUserLocation), or one-time access at specific points in time, e.g. when running a search on the server to return nearby restaurants or hotels, or current weather conditions in user’s area.

To access location this way, you need to provide an NSLocationWhenInUseUsageDescription key in your plist file, describing why you need location access, and ask for “While Using” access inside the app by calling locationManager.requestWhenInUseAuthorization().

This is what the user will see when you call requestWhenInUseAuthorization():

There’s also a class of apps that need to track location while the app is in the background, but still used in a way that can be described as “being actively used”. This includes mostly fitness apps that track user’s movement during a workout, and navigation apps. In those cases, the user won’t be looking at the screen all the time (most likely they’ll have the device locked most of the time), but they will still be doing some kind of activity related to what the app is tracking.

If you use location this way, iOS will display a very conspicuous blue bar at the top of the screen when your app is in the background, to remind the user that the tracking is still in progress:

In all above scenarios, the app has no reason to access user’s location while they aren’t actively using the app – it would only unnecessarily drain the device’s battery, so the “While Using” access level is completely sufficient.

“Always” access

The second way is to ask the user for access to their location 24/7, regardless of what they’re doing at the moment. Very few apps should really need this; this might include apps that suggest the user new places to see as they move around, e.g. while they’re visiting a new city, or apps that need to perform some actions when the user arrives at or leaves a place, e.g. showing reminders about things to do when you get back home (although in that case it might be enough to use UNLocationNotificationTrigger from the UserNotifications framework).

There are a few different modes that differ in how often and at what moments you’re notified of location changes:

the “significant change” API that notifies you when the user moves a significant distance from the previous position (e.g. a few hundred meters)

the region monitoring API that notifies you when the user enters or leaves predefined regions

the beacon monitoring API that tracks when the user approaches recognized beacon devices

and you can also use the standard, most precise (and most battery-draining) tracking API

To access location in one of these modes, you need to provide an NSLocationAlwaysUsageDescription key in your plist file, describing why you need constant location access, and ask for “Always” access by calling locationManager.requestAlwaysAuthorization().

This is what the user will see:

It’s strongly recommended not to ask for “Always” access unless you have a really good reason for it – a lot of privacy-conscious users would consider that creepy and would be likely to reject location access to your app completely, suspecting that you might be using this data for some shady purposes.

Combining both access levels

If you only provide the NSLocationAlwaysUsageDescription Info.plist key but not NSLocationWhenInUseUsageDescription, the location access menu for your app in Settings/Privacy will look like this:

This forces the user to make an “all or nothing” decision, especially since in many cases not giving location access to your app makes the app useless.

However, if it makes sense in your app, you also have an option of providing both keys and giving the user an option to fall back to “While Using” mode:

This lets the user choose between two access levels, depending on how important privacy is to them and much they trust you. You can handle this in your app in two ways:

Ask for “While Using” access first (requestWhenInUseAuthorization()). The user will probably grant you access, and then after some time, ideally after the user had a chance to use your app a little bit, you can ask again for “Always” access with requestAlwaysAuthorization() – if they refuse, then you still have the basic access.

Ask for “Always” access immediately. The user can accept or reject it, and then they can go to the settings and change the access level manually to “While Using”.

When you ask the user for “Always” access once you already have “While Using” access, the dialog the user will see looks similar, but instead of “to access your location” it says “to also access your location”:

It was always commonly understood that providing a fallback to “While Using” was a good thing to do, something that was expected from a good iOS citizen. It was however never enforced, and some developers either didn’t know better, or didn’t care, or just didn’t do it despite knowing it would make people angry. The best known example is probably Uber which made a change to their app last year, removing a “While Using” access option which was available earlier. I wouldn’t be surprised at all if the Uber issue was specifically what made Apple consider the changes made in iOS 11, because it triggered a number of blog posts and Radars asking Apple to give users more control in this matter.

Which is exactly what they did 🙂 🎉

How it works in iOS 11

If you only use “While Using” location access, as far as I understand nothing changes for you. If you use the “Always” access though, and you didn’t provide a “While Using” fallback as you should have… well, you will need to do that now 😈

As Brad Jensen explains in the WWDC talk I mentioned, Apple has decided that forcing the user to give the app “Always” location access is a bad user experience:

Now, a second reason we think that many developers choose to require Always authorization is they’re simply trying to give their users the best possible experience (…) but this makes for a very poor user experience for the users that don’t wish to grant the app Always authorization. They are forced to choose between granting the app Always, which is more than they would like in this example, or granting it Never, which means they don’t get to benefit from any of the app’s location-based features. And their final option is to grant it Always and then revoke that authorization after they’re done using the app. In any case, the user is not having a great time with this app.

So when you build your app using the iOS 11 SDK, you are now required to provide an NSLocationWhenInUseUsageDescription key in all cases (if you use location at all). For “Always” access, you can additionally provide a second key, which is now called NSLocationAlwaysAndWhenInUseUsageDescription. If you only provide NSLocationAlwaysAndWhenInUseUsageDescription but not NSLocationWhenInUseUsageDescription, asking for “Always” access will not work:

This app has attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain both NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription keys with string values explaining to the user how the app uses this data

The new name NSLocationAlwaysAndWhenInUseUsageDescription was introduced because the message will now be shown in a slightly different context, and you might want to use slightly different copy. Apple recommends that the message should explain how the app is going to use the location data in both modes:

So your description should explain to the user what features will be enabled if they select WhenInUse authorization, and then what additional features they’ll receive if they choose to grant your app Always authorization.

The old key NSLocationAlwaysUsageDescription is now only needed for backwards compatibility, if you’re still making the app available to iOS 10 users. It’s not needed or used on iOS 11 devices.

As before, depending on your use case you can either ask for “Always” access immediately, or ask for “While Using” first and then later “upgrade” to “Always”. Apple however strongly recommends that you do the latter:

We recommend you request WhenInUse authorization first. Requesting WhenInUse authorization is a little bit easier, not asking as much from the user, but there’s more to it than that. We think your app should make WhenInUse its base experience, the first thing users encounter. Then, after the user has had a chance to explore your app and get to know all of its features, when they discover that one that requires Always, they’ll try to turn it on and at that point your app can display a transition prompt that requests Always authorization on top of the WhenInUse authorization you already have.

When this prompt appears, the user will once again have that intuitive context they need in order to understand why your app is requesting Always and they’ll be much more likely to grant it to you.

This is what the user will now see when you ask for “While Using” access – almost the same as before, just with a minor wording change:

And this is what they’ll see when you ask to “upgrade” to “Always” access – the buttons are now different, in order to make it more clear what each option means:

However, the biggest change is how the dialog looks when you ask for “Always” access immediately:

As you can see, the user will now always have an option of which access modes to grant your app. They will not have to manually open the Settings app later and find your app there in order to choose the mode they want.

So one thing you’ll definitely need to update are the Info.plist keys. However, depending on how your app uses location data, you might need to change a bit more than that. If for some reason your app has depended on the fact that the app is granted “Always” access, it could interpret “While Using” access as no access and show the user some confusing error messages, or just not work properly. This will surely be a bad user experience and would probably result in some 1-star reviews on your App Store page, but you’d also be likely to just get rejected by the App Store review team if your app behaves like this. So make sure you test this really well before releasing an iOS 11 compatibility update.

Effect on existing apps

Most SDK changes usually only take effect once you recompile your app with the latest version of the SDK & Xcode. This one is different: even if you don’t release any updates this summer, this change will affect you.

Here’s what the user sees when they run existing apps from the App Store in iOS 11 beta and they ask for location access:

The first one is from Foursquare, which has provided both access level options for a long time. In this case, iOS 11 lets you immediately choose “While Using” or “Always”, and since the description texts have not yet been updated with the new behavior in mind, it shows both texts at the same time.

The second one is from Topo Maps, an app that lets you browse hiking maps and find yourself on the map while you’re on the trail. You can imagine it has no reason to track you in the background, but still, the developer has not provided a “While Using” option. iOS 11 now lets you choose that option anyway, but since it might possibly cause some unexpected behavior in the app, it shows a warning that some parts of the app might not work.

If your app has only provided an “Always” key so far, I’d recommend that you don’t wait for the final iOS 11 release (you can’t upload builds made with Xcode 9 until the last week of beta) and instead prepare a transitional update that adds the NSLocationWhenInUseUsageDescription key and makes sure the app works properly in this mode. That way, the user will get a popup like the one in Foursquare, which at least looks less scary.

The blue status bar

Remember that blue bar which is displayed when fitness apps track you in the background during a workout? Apple has made another big change here, specifically that apps with “Always” location access level will also show the blue bar when accessing your location in the background. In depends however on which API you’re using to get update callbacks.

Previously, the decision to show the bar or not was made based on the access level: the “While Using” apps always showed the blue bar in the background, and the “Always” apps never showed it, even if they used exactly the same precise location tracking API (startUpdatingLocation()).

Apple says that the blue bar is a nice feature from the user’s perspective, and they want users to have access to this feature regardless of the app’s access level. It not only gives them more control over how apps use their location, but they also let them jump back to the app quickly from any place in the system:

Suppose you have a navigation app that is granted Always authorization. If the user is in a navigation session, they probably want to be able to return to that very quickly after briefly using another app, but in iOS 10 Always authorized apps don’t get this blue double-height status bar. We felt that these problems made it difficult for users to correctly interpret how apps were using their location.

So now the blue bar will be displayed for any app that tracks location in the background using the precise tracking API (startUpdatingLocation()), regardless if they requested “While Using” or “Always” access. It will not however be shown for apps that use less precise APIs like the significant location change API or region monitoring API.

If your app uses the “Always” access level and the precise tracking API, this might be a problem for you – if your app calls the API at regular intervals throughout the day, the user will constantly be reminded about it by the blue bar switching on an off. And if they weren’t even aware that your app is doing this, it might be quite a big surprise for them… 😈

So depending on your use case you might want to decrease the frequency of polling the API, or change the app to only track location while the user is performing some relevant real-world activity, or perhaps switch to one of the less precise APIs like the significant change API. In any case, you’ll definitely want to test this, and you’ll want to do it ASAP, since the iOS 11 beta has already been made available to everyone who wants to try it.

The tracking arrow

There’s one additional minor change: see that location arrow in the status bar on the screenshots above? You might have not given it much thought before, but its behavior is also changing in iOS 11.

The arrow can be showed as a filled icon or as an outline. Previously, the outline version meant that location is being tracked in “geofencing” mode, e.g. by region tracking. A filled version was used for both the precise tracking and the significant change tracking.

The legend on the “Privacy/Location Services” list in the settings explains the behavior of the arrow as shown on that list (the one in the status bar works the same, except it shows one status for all apps, and only uses one color):

Apple has decided that this approach was not fair towards apps that use the significant change API, which were only getting data occasionally, but were treated the same as apps using the precise API.

For example, let’s consider those two apps again; the continuous background location and the significant location change monitoring app. Suppose the user goes for a run with the continuous background location app. They’ll go on the run, they’ll come back, they’ll see the solid arrow the whole time, and when they look at their map, they’ll see every twist and turn they took. When they install that app that uses significant location change monitoring, they’ll see the same thing, a solid arrow.
As far as the user is aware, this app is probably receiving the same amount of information as their run tracking app. So if users are misinterpreting our signals, we decided the best way to fix this was to adjust how we indicate location usage.

So in iOS 11, the arrow uses a more unified and simpler approach regardless of the API used: the arrow is shown filled when the app is actively receiving data, and is shown as an outline if it’s listening for data, but has not received any in a while:

From some quick testing, it seems that the arrow stays filled for about ~10 seconds after a piece of location data is read. So if you’re using the significant change API, the arrow will be filled for 10 seconds when you receive a new location, then will stay an outline e.g. for a few minutes, then will turn filled again for 10 seconds, and so on. Same if you use region monitoring – it will turn filled for 10 seconds when you receive info about a region and then turn to an outline again. If you use the precise tracking API, you will probably be receiving callbacks every second or two, so the icon will stay filled all the time.

19 comments:

James

Friday, 14 July 2017, 17:04

Thank you for this thorough writeup. It's super helpful. Cheers!

Doni

Monday, 17 July 2017, 14:13

That's a good update towards better privacy. I always feel annoyed as a user whenever an app forces me to either give an "always" permission or no permission at all.

However, even after the new feature launched, an app can always force user by stating it won't work if they only give "when in use" permission (same as Topo maps did to their app).

Do you think Apple will reject those kinds of app that force "always" permission (the way Topo Maps did)?
Or is it more flexible as it is up to the developer whether they concern about the user experience?

@Doni: they didn't say explicitly they would reject apps, but IMHO that's very likely, given how they speak about this change, mentioning that While Using should be a "base experience". Also, making the app unusable with While Using would go against this whole change, and Apple really doesn't like it when a developer does something blatantly against their own intention...

Evan Jones

Wednesday, 26 July 2017, 00:25

"So now the blue bar will be displayed for any app that tracks location in the background using the precise tracking API (startTrackingLocation()), regardless if they requested “While Using” or “Always” access. It will not however be shown for apps that use less precise APIs like the significant location change API or region monitoring API."

What about for those that use Beacons? Right now with iOS 10 we get a purple outline telling users we are using a geofence when in reality we are only trying to see if people are close to eachother

Kuba thanks for taking the time to document this! Wondering if you've noticed or heard of other issues in iOS 11 with 'Always' and apps running long standing in the background. We've implemented all the required iOS 11 plist changes (i.e. similar to Topo Maps). But are experiencing some odd behavior such that the the app works initially in background but after 6-7 hours core locations seems to drop of (not providing location/speed). The app doesn't appear to be crashing but we no longer get location info from CLLocationManager. Again this is specifically using the 'Always' option. Thanks!

Super helpful to figuring out what I needed to chang to make it work with iOS 11.

Anders

Tuesday, 26 September 2017, 08:51

I have a question with the new logic. If a user says location while using and is asked if always and declines. Then i later want to upgrade the user to always - is there then a way of checking if the user has already declined always once so we can never ask again. If we can figure this out we can enable a display of the settings where a user can be asked to do it manually.

@Anders: I think the best approach is to set some kind of flag in UserDefaults when you ask the first time, and then check if that flag is set. I don't think you can determine that using the CoreLocation SDK only.

Nandhakumar

Tuesday, 31 October 2017, 10:44

in iOS 11 startMonitoringSignificantLocationChanges working fine in background but not working if killed the app.
But in iOS 10 its working in both background and killed state.
Please help on this.

I am using NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription both key i added in plist. Always authorization i am getting. Even in background its working fine. but if kill the app location update is not working.

Ben

Thursday, 2 November 2017, 02:30

Kuba, thanks for this writeup. This is a great summary of the changes to location in iOS 11.

I've been trying to track down a bug in my app for weeks and am hoping you can shed some light on it. Do you know what conditions fulfill the "under certain conditions" provision of the hollow status bar arrow? My app does not use geofencing and explicitly shuts down the location manager when placed into the background, but the hollow arrow remains in the status bar. It never changes to a solid arrow.

It's begun impacting the battery life of some of our users and shows as "Background Activity" under Battery Usage in iOS Settings.

@Mauricio: Hey! I don't understand, why are you sending the user to Settings on first launch? If you want access to location, then you should first ask for location access, and sending them to settings is only needed if you've asked once before and they rejected it then...

Or do you mean that you're asking for location permission immediately when the app starts and that's why Apple rejected it? What did they say?