Requesting location: Core Location

It should come as no surprise that Apple considers a user's location to be private, and that means we need to ask for permission to use it. How you ask for permission depends on what you're trying to do: would you like the user's location only when your app is running, or would you like a user's location even when your app isn't running?

You might think that you'd only ever want location access when your app is running. After all, what's the point in asking for information when your app isn't around to use it?

There are times you'll want both. For example, if you're creating a map app that shows users how to get from their current location to your nearest store, you'll only need their location when the app is being used. But if you're creating an app that needs to be woken up when the user reaches a location, then you'll need access even when the app isn't running – iOS monitors the user's location on your behalf and automatically starts your app as needed.

Requesting location access requires a change to your apps Info.plist file, which is the property list file we met in project 16. Depending on whether you want "always" access or just "when in use" you need to set one key or the other. Select your property list, then go to the Editor menu and choose Add Item. Now change the name of your new item to either:

“Privacy - Location Always Usage Description” if you want to have the user's location even when the app isn't running.

“Privacy - Location When In Use Usage Description” if you only want the user's location when the app is running.

You should make sure the type is set to String, then in the value field enter some text to explain to users why you want their location. For example, "We want to help you find your nearest store." When your user is prompted to grant location access, this text will be shown alongside Apple's own descriptive message.

That's enough knowledge to get this app jump started, so open up Main.storyboard and place a label in there. Give it the custom font System Thin size 40, then give the text "UNKNOWN". For constraints, please center it horizontally and vertically. Now create an outlet for it using the assistant editor, and name the outlet distanceReading.

That label will show one of four messages depending on how close we are to our test beacon, which of course might be an iPad acting as a beacon if you don't own actual hardware. Because iBeacons use very low energy levels, their range is limited and also easily interrupted; even something as simple as turning your back to the beacon weakens its signal dramatically. Based on the beacon's distance to us, we'll show either "UNKNOWN", "FAR", "NEAR" or "RIGHT HERE".

Apple restricts your ranging to these values because of the signal's low energy nature, but it's more than enough for most uses.

To complete our current step, let's make sure we have location configured correctly. This bit will work fine on the simulator, because although the simulator isn't capable of detecting iBeacons it can simulate general location information well enough.

Open ViewController.swift and add this import alongside UIKit:

import CoreLocation

Now add this property to your class:

var locationManager: CLLocationManager!

This is the Core Location class that lets us configure how we want to be notified about location, and will also deliver location updates to us.

That doesn't actually create a location manager, or even prompt the user for location permission! To do that, we first need to create the object (easy), then set ourselves as its delegate (easy, but we need to conform to the protocol), then finally we need to request authorization. We'll start by conforming to the protocol, so change your class definition to this:

Creating the object and setting the delegate are easy enough, but the requestAlwaysAuthorization() call is new. This is where the actual action happens: if you have already been granted location permission then things will Just Work; if you haven't, iOS will request it now.

Note: if you used the "when in use" key, you should call requestWhenInUseAuthorization() instead. If you did not set the correct plist key earlier, your request for location access will be ignored.

I slipped one other thing in there: I set the view's background color to be gray. As well as changing the label's text, we'll be using color to tell users how distant the beacon is.

Requesting location authorization is a non-blocking call, which means your code will carry on executing while the user reads your location message and decides whether to grant you access to their location.

When the user has finally made their mind, you'll get told their result because we set ourselves as the delegate for our CLLocationManager object. The method that will be called is this one:

Put that into your view controller class somewhere, then run your app. It's important to test it before continuing, because if you've made a mistake somewhere it's hard to know unless you stop and check. The most common error is misconfiguring the plist with location privacy settings, so if you don't see a message requesting location access then check there first.

The didChangeAuthorization method we just added doesn't do anything because it just has a comment saying // do stuff. We'll fill that in with great stuff shortly, but for now look at the conditional statements wrapped around it: did we get authorized by the user? If so, is our device able to monitor iBeacons? If so, is ranging available? (Ranging is the ability to tell roughly how far something else is away from our device.)

Become a Swift guru

I wrote a book called Pro Swift that's dedicated to helping you master the Swift language, and it includes seven hours of video demonstrating each technique!