March 10, 2015

iOS: CoreLocation Framework and Beacon Detection

Beacon technology is one of the exciting technologies that have enabled location awareness possibilities for apps. Because of the increasing popularity of the technology, iBeacon was introduced, which extends CoreLocation Framework in iOS for beacon detection using monitoring and ranging. The signals from a beacon can now be monitored using any iOS device that supports Bluetooth 4.0.

Using CoreLocation Framework with iBeacon Technology:

To use iBeacon technology for beacon detection, we first need to import CoreLocation and CoreBluetooth into our project and include the header files of these frameworks in our ViewController header file.

#import <CoreLocation/CoreLocation.h>

#import <CoreBluetooth/CoreBluetooth.h>

Once these frameworks are included in the view-controller, we use CLLocationManager class for all the location-related activities. CLLocationManager class is the central point for configuring the delivery of location to our app. These are the main activities related with beacon detection for which support is provided by CLLocationManager class:

  1. Monitoring various regions and notifying whenever the user enters or leave the region.
  2. Reporting the range from nearby beacons.
  3. Deferring the delivery of location updates, even when the app is in background.

Since the above three activities are at the core of beacon detection, active location services are must for a robust application interacting with beacons.

Initialize Location Services: The first thing that we need to do is to initialize location manager and set ourselves as the delegate.

self.locationManager = [[CLLocationManager alloc] init];
self.loacationManager.delegate = self;

Setting distance filter and desired accuracy: Distance filter signifies the minimum distance required before an event is generated and desired accuracy sets the accuracy that we require of an application.

self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;

The other values that desired accuracy variable can have are-

  1. kCLLocationAccuracyBestForNavigation
  2. kCLLocationAccuracyBest
  3. kCLLocationAccuracyNearestTenMeters
  4. kCLLocationAccuracyHundredMeters
  5. kCLLocationAccuracyKilometer
  6. kCLLocationAccuracyThreeKilometers

Setting distance filter as kCLDistanceFilterNone signifies that we want to be notified as many times as possible hence, keeping the distance filter to the minimum.

Setup a Beacon Region:

Beacon region is the area around a beacon that is defined by the device’s proximity to the beacons. The process of detecting beacons by an application can be explained in two steps- Monitoring and Ranging. A beacon region can be defined as follows-

// Create a NSUUID with the same UUID as the broadcasting beacon
    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:UUID];
// Setup a new region with that UUID and same identifier as the broadcasting beacon
    self.myBeaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:BeaconRegionIdentifier];
// Notify whenever app enters or leaves a beacon region. 
    self.myBeaconRegion.notifyOnEntry = true;
    self.myBeaconRegion.notifyOnExit = true;

Keeping Location Services always active:

Whenever we need our application to continuously track someone’s movement, we require location services to be always active. But, because of heavy battery consumption, iOS kills the app after some time once the app is in background. Although keeping location services constantly active should be avoided (except in the cases when it’s necessary), it can be achieved by following certain steps.

In order to ensure that the app is continuously monitoring and ranging for beacons even when the app is in background (display of the device is still on), we need to set this property to true.

// Notify whenever app enters or leaves a beacon region. 
  self.myBeaconRegion.notifyEntryStateOnDisplay = true;

But, it is noticed that even after this, the application stops ranging for beacons once it is out of all the beacon regions.

// Notify whenever app enters or leaves a beacon region. 
// Tell location manager to start monitoring for the beacon region
    [self.locationManager startMonitoringForRegion:self.myBeaconRegion];
    [self.locationManager startRangingBeaconsInRegion:self.myBeaconRegion];

-(void)locationManager:(CLLocationManager*)manager didExitRegion:(CLRegion *)region
	[self.locationManager stopMonitoringForRegion:self.myBeaconRegion];

Once the ranging is stopped, it might take 30 seconds to 5 minutes for the app to retain ranging. In order to avoid this, we should make sure that the ranging for beacons is never stopped. In other words, we shouldn’t call

[self.locationManager stopMonitoringForRegion:self.myBeaconRegion];

in didExitRegion location manager delegate.

Apart from that, in order to make sure that iOS doesn’t stop location updates when the app is idle for some time and is in background, we should set this property as false.

self.locationManager.pausesLocationUpdatesAutomatically = false;

Exception in iOS 8:

It is observed that even after following above steps, the location services keep turning off in iOS 8 device. To make sure that the proper beacon detection takes place in iOS 8 devices too, we need to do two extra things –

  1. Add one or both of the below keys to Info.plist file –
    1. NSLocationWhenInUseUsageDescription
    2. NSLocationAlwaysUsageDescription

We also need these before starting location updates. The first one is for location updates in both the foreground and background, and the second one is for foreground location updates only.

    [self.locationManager requestAlwaysAuthorization];
    [self.locationManager requestWhenInUseAuthorization];

If the application supports various iOS versions, the above code snippet should be included only for iOS 8, as it will cause the application to crash for other iOS versions. One method to achieve this is as follows –

    if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        [self.locationManager requestAlwaysAuthorization];

Once all this is done, start the location updates by calling this method.

[self.locationManager startUpdatingLocation];

Various delegates called on beacon detection:

-(void) locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *) region 


1. Whenever the app enters a beacon region, this delegate gets called.

2. This delegate is called as soon as the app is outside of all beacon regions.

-(void)locationManager:(CLLocationManager*)manager didExitRegion:(CLRegion *)region {

3. This delegate is called repetitively and gives the proximity of app from beacon and other details about the beacons.

didRangeBeacons: (NSArray *) beacons 
inRegion:(CLBeaconRegion *)region

Among the above three, the first two events are fired while monitoring for beacons and the third one is fired with beacon regions-ranging.