Streaming is available in most browsers,
and in the Developer app.
-
What’s new in location authorization
Location authorization is turning 2.0. Learn about new recommendations and techniques to get the authorization you need, and a new system of diagnostics that can let you know when an authorization goal can't be met.
Chapters
- 0:00 - Introduction
- 1:52 - Authorization goals
- 9:25 - Session lifecycle
- 13:29 - Diagnostic properties
Resources
-
Download
Welcome! I’m Adam Driscoll, I’m an Engineer on the Core Location team, and I’m here today to tell you about what’s new with location authorization! If you’re watching me now, you probably know that Core Location is how apps, like Maps here, can find out where you are.
Your app can do the same. It might show people interesting things happening nearby, or guide them through an exhibit by using Core Location API.
Last year my teammates Siraj and Nivash introduced two new API objects: CLLocationUpdate and CLMonitor. Each one presents an AsyncSequence interface that makes it easy to get latitude and longitude updates whenever the device moves, or state-change events whenever spatial conditions you describe become satisfied or unsatisfied.
Before you can use those APIs, you need to get permission from each person using your app. Core Location Authorization API is how you do this, but in Swift it can get pretty complicated! So today, I’m going to help you collapse all this code down to this.
And we’ll make it more robust in the process! The key to all this is CLServiceSession and a new system of Diagnostic Properties, which I will tell you all about today, in three main topics! First, CLServiceSession is the new declarative way to tell Core Location your authorization goals, what you need for each feature of your app, while it is running.
Then, since it’s a session object, I’ll dive a little deeper into when you should create one, and what happens to it when your app is suspended or terminated.
And finally, I’ll tell you about an all new system of Diagnostic Properties: These give your app a complete, contextual understanding of its current authorization, and of the functioning of any other Core Location API objects too.
But first, goals. Moving from a procedural model, to a declarative one, for interacting with location authorization.
This is what that authorization looks like, for the LocationMonitorSample app in Settings. Between the Precise Location toggle and the options above if we made a diagram, and simplified a bit, it might look like this.
Every app starts in NotDetermined. Then if people choose to authorize that app into one of the states to the right, they can choose to give it full precision or only approximate access.
So to start this process, until now you needed to ask for authorization, using a CLLocationManager and a method such as requestWhenInUseAuthorization, which can take you from NotDetermined to either WhenInUse, or Denial. Well this is a clear enough way to ask, but maybe the person normally uses your app in ways that only require their approximate location, and so that is how they authorized it. Then some day they ask for turn-by-turn directions, or engage some other feature that obviously can only work with full accuracy location information. You’ll want to call requestTemporaryFullAccuracy withPurposeKey, to give them the chance to move up temporarily, but temporary authorization lapses. What if they really granted you only temporary WhenInUse authorization with Allow Once in the first place, then ducked out to chat with a friend for a moment? You’re back to needing to call requestWhenInUseAuthorization again, and maybe requestTemporaryFullAccuracy withPurposeKey: again, too! Really, to give turn-by-turn directions in this example, what you need is full-accuracy WhenInUse authorization and that authorization goal, to give it a name, has driven the selection of what to do at each step in this diagram so far! CLServiceSession, new this year, lets you express Authorization Goals directly, and tie them to the task that motivates them. In our example we acknowledged that some people might normally grant only approximate access, and that’s ok in general. So you can set a goal that respects that choice by instantiating and holding a CLServiceSession like this, with .whenInUse as the argument. Core Location will present requests to each person as appropriate to try to get your app into one of these states.
Then someday, if they do engage that navigation feature, create an additional CLServiceSession with a fullAccuracyPurposeKey to narrow-down your goal. Think of this like layering: because your app’s normal purpose remains, but now it is additionally doing something that temporarily requires full accuracy, go ahead and keep the original session, and add this new one too. Anyway, this "Nav" key will be looked up in your location authorization purpose localization resources, so in case Core Location has to ask for temporary full accuracy authorization to achieve this goal, you have provided a string describing the navigation purpose that will make sure everyone understands why full accuracy is needed now.
I’ve mentioned a few things that bear repeating. First is that CLServiceSession objects flip the script a bit: they don’t tell Core Location what to do, they tell Core Location what you need. This means you don't have to worry about a whole class of hard-to-test-for problems, like your app being backgrounded at the wrong moment when it meant to ask for authorization. Will it recover? Yes because you hold a CLServiceSession, so Core Location knows your goal, and will act when next possible to try to get you there.
Related to that, CLServiceSessions should be taken proactively. For example, hold one requiring full-accuracy when people engage a feature that would warrant a special ask for it, even if your app actually already has full accuracy.
How do you recognize when a feature warrants full-accuracy in this explicit way? Think about why someone might want to use your app with their approximate location after all, they could have denied sharing it altogether, but didn’t.
Then what are the special features, the case-by-case features someone might feel warranted an exception if they used them? Hold a full-accuracy service session only if and when those features are used.
And finally, CLServiceSessions are literally immutable, sendable objects. But generally think about layering them, not replacing them, as your needs change.
If your app has different components driving different features with different relevant authorization goals, and those features sometimes layer or nest, then those components shouldn’t have to coordinate over what kind of service session to create, they should just individually create the one they respectively need during the time they need it. Make each layered feature self-sufficient in service sessions, and Core Location will take care of the rest.
Speaking of layering! I’ve been talking about the brand new CLServiceSession struct all this time. But each of the APIs we introduced last year also define sessions of a sort when their liveUpdates or events sequences are iterated.
In many cases, if your app is iterating liveUpdates, for example, it has a goal of receiving them, right? So Core Location is able to treat the act of iterating liveUpdates or Monitor.events as implicitly holding a CLServiceSession.
This is awesome, because it means you may be able to update your app to handle Core Location authorization with service sessions, just by deleting code. In fact, that’s all updating last year’s sample app for CLLocationUpdate needed.
This behavior is enabled by default, so you can lean on implicit service sessions to define your authorization goals.
Each modern Core Location API which provides an async sequence to be iterated, will represent a goal authorization state of .whenInUse while iterating, so that really nice code snippet for handling authorization I showed you a few minutes ago, actually gets even nicer.
I hope you’ll really like it, but I know that sometimes, especially in large projects, there are moments when it would not be appropriate to ask the person using your app for authorization. And it could be awkward at those times to completely prevent your code from iterating updates.
So if you don’t want API-use to implicitly motivate authorization requests in your app, then the effect of these implicit sessions can be disabled entirely by setting the NSLocation-Require- Explicit-Service-Session key in your app’s Info.plist. after which you just need to take that explicit session again.
So there are three main reasons why you might want to take actual CLServiceSession objects, instead of just relying on implicit ones. First, implicit service sessions don’t require full-accuracy, because for the most part your app should respect accuracy limitation if people place it.
Only when you really do need full-accuracy, be explicit about it.
Similarly, Core Location does not assume your app will need always authorization. So if you really do, you’ll need to take an explicit CLServiceSession with .always as its goal whenever you may wish to use the extra access it grants. Note that starting this year, Always authorization will only be effective when you hold one of these, and you can only start holding one when your app is in the foreground. I’ll talk more about this when we look at Session Lifecycle in a moment.
And finally, in conjunction with the info.plist key, you can create explicit service sessions at particular times, or with the .none authorization goal, to take more control over when Core Location will ask people for authorization on your app’s behalf. Apps with many internal uses of Core Location, and ones which are happy to let people take the lead in whether they should use location information or not, may find this approach simplest. Ok, so you’re taking CLServiceSessions or leveraging the implicit sessions you get from iterating liveUpdates and events. Because these sessions are aligned with user-facing features, that means they will interact with events in your app’s lifecycle as a whole. Let’s walk through that.
Let’s visualize someone running an app, and pencil in the parts where they’re using a feature dependent on Core Location.
Some of those features may be short-lived, like geo-tagging a photo, while others may be longer. Let’s say browsing around a MapKit view for a while and features might even overlap if they look for travel time estimates, for example, while browsing. It’s easy to create and hold CLServiceSession objects around these feature sessions, just associate them in your code with whatever components implement the feature, being sure to layer up as needed. And of course by default implicit sessions will even be doing this for you.
But people don’t necessarily consume features start-to-finish within an app. If a feature like recording an exercise track naturally continues for more than a few seconds, people are pretty likely to leave it running in the background, and direct their attention to a parallel task, like fiddling with their music in a different app, before returning to the exercise-tracking app at the end of their workout.
When the feature continues conceptually, so will any implicit sessions, and so should any explicit CLServiceSession object you create.
You might be asking why. Three reasons. First, doing so asserts your app’s continued interest in its authorization goal. Core Location won’t ask people to adjust your app’s authorization level while it’s in the background, but by knowing what you need when your app comes unexpectedly back to the foreground, Core Location can immediately take steps to restore authorization if needed.
And if your app is Always authorized, liveUpdates and CLMonitor.events won’t yield results when it is not in use, unless a session which was started in the foreground, or while another one was in effect asserts that continued interest.
Finally, if those first two reasons weren’t compelling enough, it’s just simpler this way. Tie session life directly to feature span, and don’t worry about any other factors. Implicit sessions will be doing this, so you might as well do it too.
Holding sessions into the background works even if the app is later suspended during a lull in updates, but while it’s still on-tap to track that workout.
More importantly, it’s true even if the app is terminated while it’s suspended. Notice both that the workout still needs to be tracked: that’s still what the person asked this app to do. But also that the app process, and all the CLServiceSession objects or implicit sessions it contained, are now literally gone.
Core Location does not take measures to keep apps running continuously when it has nothing to deliver to them, and does not generate or deliver updates for which an app is not authorized, for example due to having .whenInUse authorization, and being backgrounded without a LiveActivity or CLBackgroundActivitySession in effect. But that said, it does keep track of each outstanding API object, whether a CLServiceSession, liveUpdates, or other sequence, when suspension or termination occurs, and continues to act on it. And that means it will resume or re-launch the app into the background when new information is ready for delivery, and the app’s authorization allows it.
That’s why the blue session bar in this diagram continues despite what is happening to the app’s actual process.
But Core Location doesn’t keep track of these explicit and implicit sessions forever. Instead, it does so for only a few seconds after the app is launched again, whether that launch was due to Core Location API events, user interaction, or any other cause. This limited period is an important measure to prevent leaks of session state, in case an app doesn’t resume that ongoing feature after all. So your job, when you’re writing an app like this one, is to make sure that your process launch logic knows what features it has been tasked with pursuing, and re-takes session objects or resumes iterating liveUpdates or Monitor.events that had been interrupted as soon as it can.
This way the new objects can take over for the old ones, and Core Location will know your app remains interested.
That brings us to Diagnostic Properties. Diagnostic Properties tell you why, when an authorization goal cannot be met.
While you can get the effect of a CLServiceSession just by holding onto it, each instance also exposes a diagnostics AsyncSequence you can iterate with for try await to learn what’s up, like you see in this code.
Then notice this boolean property. This is what we call a Diagnostic Property, and it lets you know when something unexpected happens. In this case, that the person using your app does not want to share their location with it. In addition to .authorizationDenied, there are several more, including .insufficientlyInUse for when Core Location can’t yet ask for authorization on your app’s behalf, or .alwaysAuthorizationDenied, when you have set a goal of Always authorization, but it was not granted. Let me call your attention specifically to .authorizationDeniedGlobally, which will be true if location services are disabled system-wide. If someone configures their device that way, your app will also be subject to that denial, so .authorizationDenied will be true as well.
Diagnostic Properties treat these as two different, but related properties so that if your app only wants to know when it has been explicitly denied access, say to omit travel time estimates from your UI, then it only needs to check the general one.
But if you want to provide different guidance to those people who made up their mind globally long before installing your app? You can check the more specific property as well, to find out if that’s what happened.
So looking at the code again, you can leverage this by making your diagnostic handling logic a little more neutral. Maybe you just need to know when they have made up their mind? Note that when this code runs, if someone has already granted your app authorization or denied it, then because Core Location will not need to ask them anything, the first diagnostic will arrive with the .authorizationRequestInProgress property already false, and your code will break out right away, which may be what you want.
But remember that your app’s authorization can change dynamically, if the user adjusts it in Settings, or if they actually granted only temporary authorization. So you’ll probably want to keep listening for session diagnostics as long as your app is providing the feature the session corresponds to, and maybe set some @Published vars so your UI can react.
If you scope the session, and the iteration, in a Task that you run during the time your app needs location information, and cancel when it’s done, your session will have the right lifecycle, and your UI will be able to respond appropriately given what authorization the person using your app opted to give it.
Diagnostic Properties give CLServiceSession great flexibility and power to indicate in an actionable way when it can’t achieve your authorization goal state. And as you know, liveUpdates and CLMonitor events provide implicit sessions, too. So Diagnostic Properties will also be available in each CLLocationUpdate or CLMonitor.Event.
We saw those objects before, notably CLLocationUpdate already has a boolean diagnostic property named .stationary which will be true on the last update before updates are paused because the device has stopped moving.
But now each of them will also contain additional boolean properties to address the problems that might crop up due to its role as an implicit service session.
And then some more: CLLocationUpdate gains two, explaining why further updates might not arrive for a while, .accuracyLimited as approximate location is updated only every 15 to 20 minutes, and .locationUnavailable in case Core Location is currently unable to determine their location at all.
For its part, CLMonitor.Event can also report that Events will not be yielded for a condition because of accuracy limitation, or for several other reasons, like if too many other conditions of this type are already being monitored.
Taken together, these Diagnostic Properties on each API object should allow you to get rid of timeouts and guesswork you may have needed in the past. Now, if for some reason Core Location can’t give you the update or event you’re expecting, it will at least give you one with a Diagnostic Property set to indicate why not, instead. So that’s it! Remember: You can use CLServiceSession to tell Core Location what authorization you need, if the API updates and events you naturally iterate don’t already handle that for you automatically! And now, whenever updates, events, or service sessions won’t be able to yield their intended result, Core Location will always let you know why, via Diagnostic Properties. If you want, you can find out more about CLLocationUpdate and CLMonitor from these talks last year.
So please, try these new facilities out and let us know how they work for you!
-
-
0:31 - CLLocationUpdate and CLMonitor
// Iterating liveUpdates to reflect current location Task { let updates = CLLocationUpdate.liveUpdates() for try await update in updates { if let loc = update.location { updateLocationUI(location: loc) } } } // Iterating monitor events to report condition state changes Task { let monitor = await CLMonitor(monitorName) await monitor.add(CLMonitor.CircularGeographicCondition(center: applePark, radius: 50), identifier: "ApplePark") for try await event in await monitor.events { updateConditionsUI(for: event.identifier, state: event.state) } }
-
0:52 - Handle updates with CLLocationManagerDelegate
// Adapting location authorization to Swift with a MainActor singleton @MainActor class LocationReflector: NSObject, CLLocationManagerDelegate, ObservableObject { static let shared = LocationReflector() private let manager = CLLocationManager() override init() { super.init() manager.delegate = self } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager){ if (manager.authorizationStatus == .notDetermined) { manager.requestWhenInUseAuthorization() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { // Process locations[0] } // ... }
-
1:07 - CLServiceSession simplifies
// CLServiceSession in action Task { let session = CLServiceSession(authorization: .whenInUse) for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
7:15 - Implicit service sessions
// CLServiceSession in action Task { let session = CLServiceSession(authorization: .whenInUse) for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
7:34 - Implicit service sessions
Task { for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
13:37 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (diagnostic.authorizationDenied) { // Ok, let’s let them pick a location instead? } }
-
15:00 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { // They’ve decided (maybe already). We can move on! break } }
-
15:25 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { reactToChanges(authorized:!diagnostic.authorizationDenied) } }
-
15:46 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession Task { let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { reactToChanges(authorized:!diagnostic.authorizationDenied) } } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.