Interacting with iBeacons in Swift

I’ve recently been investigating iBeacons and the programming model to interact with them in iOS using Swift.

iBeacons are a class of Bluetooth Low Energy (BLE) devices that continuously broadcast identifying information about themselves using the iBeacon protocol that Apple introduced in 2013. They are meant to be placed in the physical world at locations of interest. Mobile apps can then ask to be notified when they move within range of a beacon, and react appropriately.

There are many use cases for iBeacons. For example, consider an art museum that places a beacon near each painting, and a mobile app that displays information about the painting when a user enters the range of the beacon.

iBeacon

I recently purchased a cheaper one from Amazon to experiment with, and I put together a simple iOS application in Swift that demonstrates the programming model described here.

iBeacon Location Protocol

iBeacons advertise themselves to the world by broadcasting three values: a UUID, a major number, and a minor number.

It is common for several iBeacons to broadcast the same UUID (usually indicating that they belong to the same semantic region). As an example, consider a scenario where a grocery store uses iBeacons to track which products customers browse the most, or understand common routes that customers take through the store as they shop. The grocery store would likely use the same UUID for their iBeacons (representing the grocery store region).

The major and minor numbers allow for more specific location discrimination. For the grocery store, the major number might represent a specific aisle in the store, and the minor number might represent product type (e.g., peanut butter). Depending on which iBeacon is closest to a mobile app, the customer’s location in the grocery store can be inferred. In essence, iBeacons allow for detailed location sensing indoors, where GPS can be unreliable.

iOS Programming Model

The iOS CoreLocation library provides support to listen for and interact with iBeacons.

To interact with iBeacons, users must give your mobile app permission to access their location. You can have your app prompt users for permission by adding keys to Info.plist. The NSLocationWhenInUseUsageDescription key will prompt users for permission to access their location when your app is in the foreground. If your app needs to receive iBeacon location notifications when in the background, the NSLocationAlwaysUsageDescription key should be used instead.

iBeacon Notification

There are two modes for receiving iBeacon notifications: monitoring and ranging.

Monitoring

Monitoring is the simpler of the two modes. It can alert your app of region enter and exit events, but that’s about it. To listen for these events, instantiate a new CLLocationManager and implement the didEnterRegion and didExitRegion delegate methods. In my experience, the exit events can be delayed for about 30 seconds after you leave a region. The enter events, however, seem to happen reliably with little delay. If you start inside of a region, you will not receive an event until you leave it (and trigger an exit event).

The code below sets up monitoring for a region.


var locationManager: CLLocationManager = CLLocationManager()
if let uuid = NSUUID(UUIDString: IBEACON_PROXIMITY_UUID) {
    let beaconRegion = CLBeaconRegion(proximityUUID: uuid, identifier: "iBeacon")
    beaconRegion.notifyOnEntry = true
    beaconRegion.notifyOnExit = true
    locationManager.startMonitoringForRegion(beaconRegion)
}

The IBEACON_PROXIMITY_UUID is the UUID of your iBeacon, which comes from the manufacturer. The UUID can usually be changed, but the difficulty and procedure depends on the manufacturer.

After setting up monitoring, you can listen for enter and exit region callbacks by implementing the following two delegate methods:


func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
    if let beaconRegion = region as? CLBeaconRegion {
        print("DID ENTER REGION: uuid: \(beaconRegion.proximityUUID.UUIDString)")
    }
}
    
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
    if let beaconRegion = region as? CLBeaconRegion {
        print("DID EXIT REGION: uuid: \(beaconRegion.proximityUUID.UUIDString)")
    }
}

There are also two CLLocationManagerDelegate methods that can be helpful: didStartMonitoringForRegion confirms that monitoring was started correctly, and monitoringDidFailForRegion provides error information if something goes wrong in the monitoring setup.

Ranging

Ranging is the other iBeacon notification mode. It is substantially more informative than monitoring and sends updates to your app much more frequently. Setting up ranging is similar to monitoring, except you call startRangingBeaconsInRegion instead of startMonitoringForRegion. Once set up, you listen for didRangeBeacons callbacks. This delegate method can look like this:


func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) {
    for beacon in beacons {
        var beaconProximity: String;
        switch (beacon.proximity) {
        case CLProximity.Unknown:    beaconProximity = "Unknown";
        case CLProximity.Far:        beaconProximity = "Far";
        case CLProximity.Near:       beaconProximity = "Near";
        case CLProximity.Immediate:  beaconProximity = "Immediate";
        }
        print("BEACON RANGED: uuid: \(beacon.proximityUUID.UUIDString) major: \(beacon.major)  minor: \(beacon.minor) proximity: \(beaconProximity)")
    }
}

These callbacks can happen as often as one per second. Each callback provides iBeacon signal strength information, along with a CLProximity value, which describes your proximity to the iBeacon as being Immediate, Near, Far, or Unknown.

The caveat with ranging is that it consumes significantly more power than monitoring. Further, while you can use ranging in the background, there are several articles that I’ve read indicating that Apple may reject your app if you do.

If you’ve experimented with iBeacons, I’d like to hear about your experiences.

Conversation
  • dze says:

    I would like to know how do you get the range of the nearer beacon if you got two of them with diferrent uuid thank you.

    • Matt Nedrich Matt Nedrich says:

      There are a few options to differentiate beacon distances if you observe more than one.

      One option is to compare the CLProximity value that each of the returns. The nearer one will have a closer proximity value.

      The other option is to compare the beacon accuracy values – https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLBeacon_class/#//apple_ref/occ/instp/CLBeacon/accuracy. These values can give you an approximate distance. They aren’t direct distance measures, rather they are statistical distance values. In my experience these values are usually good enough to compare relative distances between beacons.

      • dze says:

        I have browsed on the documentation at some point however I am looking for a more concrete example online. Unfortunately I was not able to find any. Are you able to provide me a code example on how to compare two uuid proximity? I am trying to make the information of a book popup whenever I get in the range of “near” on a book. I am trying with two beacons of different UUID’s. Thank you for your help. I am new to swift. Thank you for your patience.

  • Selvarani says:

    Hi

    Here didEnterRegion and didExitRegion is called my app

    Thank you

    • Frank says:

      Hey

      Thanks for your project.
      Ranging beacons work as expected but monitoring does not work I don’t get didEnterRegion or didExitRegion.
      I am running the app in iOS 10.

      Thanks

      • Matt Nedrich Matt Nedrich says:

        Are you sure you are moving out of and back into the region? If you start the app already inside of a region you will not receive a didEnterRegion callback. Sometimes the callbacks can be a bit delayed (especially didExitRegion), but they should happen within a few minutes at most. I’ve found that didEnterRegion usually happens almost instantaneously, while didExitRegion can take up to 45 seconds to get called.

  • Comments are closed.