When beacon ranging is active on your iPhone, apps can subscribe to regular updates of the beacons in range. I’ll show you how to set up beacon ranging such that apps receive these updates both in the foreground (easy) and in the background (not so easy).
Introduction
When beacon ranging is active, CLLocationManager sends regular updates about the beacons in range of your iPhone to CLLocationManagerDelegate. CLLocationManager calls the function
locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion)
of its delegate (see Apple’s documentation for details). The parameter beacons
holds the beacons that are currently in range of your iPhone and that are contained in the given region
.
You will reimplement this function in the class derived from CLLocationManagerDelegate. Based on the beacons in range, the reimplemented function determines the location of the iPhone in a museum, airport or retail store. I’ll decribe how to locate an exhibit in a room in a future post. Here in this post, I’ll “only” describe how to configure CLLocationManager such that the function above is called no matter whether the app is running in the foreground or background.
Configuring CLLocationManager consists of four steps:
- Setting the prompt for authorising location in the app’s settings (Info.plist).
- Switching on the background mode for location updates in the app’s settings (Info.plist).
- Initialising CLLocationManager to perform beacon ranging.
- Starting and stopping beacon ranging dynamically.
Step 1: Prompt for Authorising Location Updates
When an app uses location like any app using beacon ranging does, it must ask the user to authorise the use of the iPhone’s location services because of privacy reasons. The app can request to have access to the location services “always”, when the app is in use, or “never”. When-in-use authorisation is enough for beacon ranging.
When the app requests this authorisation, it shows a dialog that can be confirmed or rejected by the user. You can customise the message shown in this dialog by assigning a text of your choice – “Required for proper indoor location”, example – to the settings key NSLocationWhenInUseUsageDescription
in Info.plist file of your project.
Step 2: Background Mode for Location Updates
If the app shall receive location updates while in the background, you must enable “Location updates” in the app’s settings. You can simply tick “Location updates” in Xcode’s project settings “<project> | Capabilities | Background Modes”, where you replace “<project>” by the name of your project.
Note that I have ticked the box for “Audio, AirPlay and Picture in Picture”, because my sample project – an audio guide for museums using beacons – plays audio in the background as well. If you need not play audio in the background, you leave this box unticked.
Step 3: Initialising Beacon Ranging
When the app starts, CLLocationManager must be initialised for beacon ranging in the foreground and background. The following code snippet shows the essential parts.
import CoreLocation class AGLocationManager: NSObject, CLLocationManagerDelegate { var locationManager: CLLocationManager let proximityID: NSUUID override init() { self.locationManager = CLLocationManager() self.proximityID = NSUUID(UUIDString: "F29DC74A-DBBD-12DE-318A-C8B046DD4425")! super.init() self.initLocating() } func initLocating() { if CLLocationManager.isRangingAvailable() { self.locationManager.requestWhenInUseAuthorization() self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers self.locationManager.allowsBackgroundLocationUpdates = true self.locationManager.delegate = self } } func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) { // Determining the location of the iPhone's user... } }
In my sample app, AGLocationManager
is the class responsible for determining the user’s location. It inherits the protocol CLLocationManagerDelegate
to receive the location updates through the function locationManager(_:didRangeBeacons:inRegion:)
.
AGLocationManager
holds an instance of CLLocationManager
from the CoreLocation
framework in its member variable locationManager
. Its constant member variable proximityID
stores the UUID of the beacon region that your app is interested in. Your app only looks at beacons whose region UUID is the same as promixityID
.
The init()
function initialises the member variables locationManager
and proximityID
. The UUID assigned to proximityID
is the one used by my sample app. You should generate a different UUID for your app. Finally, init()
calls initLocating()
, which initialises beacon ranging.
initLocating()
first checks whether your iPhone supports beacon ranging at all. Beacons are supported by iOS 7.0 and the iPhone 5S or newer. If beacon ranging is available on the user’s iPhone, the function first requests when-in-use authorisation for accessing location services. The user is bothered with a prompt only when the app is started the first time after its installation. The app should also check whether the user granted the authorisation request, which I leave out for brevity.
If the iPhone doesn’t support beacon ranging or if the user doesn’t grant access to the location services, the app should ideally fall back to a mode not relying on location services. This is not always possible with navigation apps for cars as the prime example. An audio guide for museums could fall back to entering the numbers of the exhibits manually.
The desiredAccuracy
is set to kCLLocationAccuracyThreeKilometers
(3 kilometres). This is the coarsest possible accuracy, which can easily be achieved with the help of cell towers. Wifi or GPS are not needed. Hence, there is no additional drain on the iPhone’s battery by Wifi or GPS.
The crucial setting for enabling beacon ranging in the background is the line
self.locationManager.allowsBackgroundLocationUpdates = true
Finally, we tell the CLLocationManager
to deliver location updates to AGLocationManager
by making AGLocationManager
the delegate of CLLocationManager
. This ensures that locationManager(_:didRangeBeacons:inRegion:)
gets called every time any property of any beacon in range changes or if the beacons in range change.
Step 4: Starting and Stopping Beacon Ranging
Now, everything is prepared to receive location updates. The final step is to start location updates and beacon ranging explicitly.
func startLocating() { self.locationManager.startUpdatingLocation() self.locationManager.startRangingBeaconsInRegion(CLBeaconRegion(proximityUUID: self.proximityID, identifier: "AllBeacons")) }
Note that beacon ranging will not work in the background if you do not call startUpdatingLocation()
.
Location updates and beacon ranging are stopped with the following function.
func stopLocating() { self.locationManager.stopRangingBeaconsInRegion(CLBeaconRegion(proximityUUID: self.proximityID, identifier: "AllBeacons")) self.locationManager.stopUpdatingLocation() }
Apple recommends to start location updates and beacon ranging only when it is really needed – to reduce power consumption and to extend battery life. In my sample AudioGuide app, I stop beacon ranging when audio is playing. As soon as the audio playback stops, the app restarts beacon ranging. Then, the visitor of the museum is most likely walking to the next exhibit.
Now, your app will happily receive location updates in the function locationManager(_:didRangeBeacons:inRegion:)
. I’ll show how to determine the phone’s location in a museum in a future post.