Important: This guide describes the “monolithic” Estimote SDK, from  Estimote/iOS‑SDK.

The proximity detection features in that SDK are now obsoleted in favor of the new Estimote Proximity SDK, available at  Estimote/iOS‑Proximity‑SDK.

To learn more about the new API, see the Add Proximity to iOS app guide instead.

Part 2: Background monitoring

In part 1, we’ve done all the heavy-lifting required to set up an Xcode project with an Estimote SDK. Now it’s time to make use of that and implement our app’s first feature by making it monitor beacons and show a notification with current security wait times when a user enters the airport.

What’s ahead (aka Table of Contents)

What is beacon monitoring?

You can think of beacon monitoring as a geofence, i.e., a virtual barrier that’s usually defined using a set of geographic coordinates. Moving and out of the area it encloses triggers “enter” and “exit” events, which the app can react to.

In case of iBeacon, the area is defined by the range of one or more beacons. This allows more granularity and precision than regular geofencing—the latter being based on a mix of signals from cell towers, WiFi and GPS. Beacon geofences are also more responsive: “enter” events usually take up to a few seconds to trigger, “exit” events up to 30 seconds. (Regular geofences take “3 to 5 minutes on average”, according to the CLLocationManager’s documentation.)

Best part? iOS will keep listening for those beacons at all times—even if your app is not running or was terminated, and even if the iPhone/iPad is locked or rebooted. Once an “enter” or “exit” happens, iOS will launch the app into the background (if needed) and let it execute some code for a few seconds to handle the event.

Info: If you’re already familiar with CLLocationManager and monitoring for CLCircularRegion objects, this is probably nothing new for you. Beacon monitoring is based on the exact same concepts and mechanisms—it’s just beacons, Bluetooth and CLBeaconRegion objects instead.

Beacon regions

How to define the set of beacons to use as a geofence? That’s what beacon regions are for. Beacon regions are a fundamental concept of iBeacon, so pay attention!

Beacon region is like a filter or a regular expression. If you remember from the What is iBeacon? article, each beacon is identified by three values:

  • UUID, most commonly represented as a string, e.g. “B9407F30-F5F8-466E-AFF9-25556B57FE6D”,
  • major number, an unsigned short integer, i.e., an integer ranging from 1 to 65535, (0 is reserved by the iOS)
  • minor number, also an unsigned short integer, like the major number.

With beacon regions, you can say, “I’m only interested in beacons with UUID ‘ABC’ and major ‘XYZ’.” Or you can be more specific, adding a minor requirement to the mix. Or less specific, only requiring a certain UUID. These are all the options though: UUID + major + minor, UUID + major or UUID alone.

Keep in mind: When monitoring a region that spans multiple beacons, there will be a single “enter” event when the first matching beacon is detected; and a single “exit” event when none of the matching beacons can be detected anymore. There’s no way to keep track of “interim” beacons’ “enters” and “exits”—other than creating a single region per each beacon of course.

Important: iOS restricts the number of regions (the beacon ones, and regular geofences combined!) that can be monitored at a time to 20, so for larger deployments you’ll need to plan accordingly and group the beacons you want to monitor into at most 20 regions.

Start monitoring

Let’s create a beacon region defining our monitoring geofence, and use it to start monitoring.

In an actual airport environment, you’d probably want to target multiple beacons for that. You could, for example, assign a single major value to all the beacons at the entrances, and use a UUID + major combination to define an “entrance” region.

For the purposes of this tutorial, we’ll stick to a single beacon.

// this is were we left off:
self.beaconManager.requestAlwaysAuthorization()
// add this below:
self.beaconManager.startMonitoring(for: CLBeaconRegion(
    proximityUUID: UUID(uuidString: "B9407F30-F5F8-466E-AFF9-25556B57FE6D")!,
    major: 123, minor: 123, identifier: "monitored region"))
// this is were we left off:
[self.beaconManager requestAlwaysAuthorization];
// add this below:
[self.beaconManager startMonitoringForRegion:[[CLBeaconRegion alloc]
    initWithProximityUUID:[[NSUUID alloc]
        initWithUUIDString:@"B9407F30-F5F8-466E-AFF9-25556B57FE6D"]
    major:123 minor:123 identifier:@"monitored region"]];

As you’re adding this code, replace the example UUID, major, and minor values with those of one of your beacons. The easiest way to go about that is to log in to Estimote Cloud and copy-paste them off your “Beacons” dashboard.

Note: By default, beacons are assigned to the e-mail address provided when placing an order. You can transfer them to another Estimote Account by following this short guide.

Another option is to get the Estimote app and to find your beacon on the radar in the “Devices” section. Tap on it, and it’ll bring you to the “Beacon Details” view, with the beacon’s UUID, major, and minor right there.

Tip: If there’s a lot of beacons nearby, it might be easier to find the one you want using the “List” view instead of the “Radar” view. The list shows the beacons sorted by their approximate distance, so put the beacon you’re interested in next to the device and it should bubble up to the top.

You can switch to the “List” view using the tab bar at the bottom of the “Beacons in range” screen.

Keep in mind: Monitored regions persist until stopMonitoringForRegion is used to stop them, or the app is uninstalled. This is especially important during development: deleting a startMonitoring line won’t stop the beacon manager delegate from receiving monitoring events for a region. If you want to clean up, you can either uninstall the app or use the monitoredRegions property of a beacon manager to stop monitoring for regions that are no longer relevant.

Show an “enter” notification

Time for some real action—let’s add a notification to show up whenever user enters the range of our monitored beacon.

We’ll use a simple static string as a message. If building an actual airport app, we’d fetch all the data dynamically from the airport’s API. It’s the 21st century, surely every airport has one! (-:

Keep in mind: By default, iOS only gives your app a few seconds of execution time to handle the “enter” and “exit” events in the background. If you need more (e.g., to handle long-running API requests), start a background task.

In the AppDelegate, add the following implementation of the didEnterRegion delegate method:

func beaconManager(_ manager: Any, didEnter region: CLBeaconRegion) {
    let notification = UILocalNotification()
    notification.alertBody =
        "Your gate closes in 47 minutes. " +
        "Current security wait time is 15 minutes, " +
        "and it's a 5 minute walk from security to the gate. " +
        "Looks like you've got plenty of time!"
    UIApplication.shared.presentLocalNotificationNow(notification)
}
- (void)beaconManager:(id)manager didEnterRegion:(CLBeaconRegion *)region {
    UILocalNotification *notification = [UILocalNotification new];
    notification.alertBody =
        @"Your gate closes in 47 minutes. "
         "Current security wait time is 15 minutes, "
         "and it's a 5 minute walk from security to the gate. "
         "Looks like you've got plenty of time!";
    [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}

Info: If you’re not familiar with the delegate pattern, Apple has a Delegation chapter in their “Cocoa Core Competencies” guide that explains it briefly. For even more details, you can take a look at the Delegates and Data Sources chapter from the “Concepts in Objective-C Programming” guide.

One more thing: remember requesting access to Location Services to use iBeacon features? We need to go through a similar process for our app to be able to show notifications. In the AppDelegate’s didFinishLaunching method, add:

UIApplication.shared.registerUserNotificationSettings(
    UIUserNotificationSettings(types: .alert, categories: nil))
[[UIApplication sharedApplication]
 registerUserNotificationSettings:[UIUserNotificationSettings
                                   settingsForTypes:UIUserNotificationTypeAlert
                                   categories:nil]];

Test monitoring: “flip to sleep”

Testing the lock screen icon was easy—all we had to do was launch the app on the device while in range of the beacon, and the icon showed up on the lock screen immediately.

To test our notification, we need to trigger the “enter” event—but as it turns out, simply launching the app when in range of the beacon is not enough. That’s because iOS considers an “enter” event when a transition from the outside to inside range happens—and we weren’t outside range to begin with. (Note that same goes for the “exit” event, i.e., it’ll only get triggered when a transition from the inside to outside happens.)

Tip: If you need to know the initial state of the beacon region (i.e., inside vs outside) without waiting for a transition to happen, you can use the didDetermineState delegate instead. It’ll be called when starting monitoring, with the initial state of the beacon region, and then each time the state changes.

You can also force iOS to call the didDetermineState with the current state by using beacon manager’s requestStateForRegion method.

In other words:

  • we launched the app with the initial state of the beacon region being inside
  • we need to move outside
  • then, we move back inside—and that’s what’s going to trigger the “enter” event we want

Now, the obvious way to go about this would be to move the beacon and the device physically away from each other, and then bring them back together … which is great the first time you to do it, but long-term, constantly walking back and forth to test monitoring doesn’t sound too convenient.

Fortunately, there’s a better option—use the Estimote app to enable the “flip to sleep” mode:

  1. Go to the “Devices” section, find your beacon on the radar or the list and tap on it.
  2. If you’re not yet logged in, you’ll be asked to do it now. (As a security measure, you can only change settings of your own beacons, and logging in with your Estimote Account allows the app to confirm the ownership.)
  3. Find the “Sensors” section and tap the “Beacon In Motion” row.
  4. Enable the accelerometer, then tap “Flip to Sleep Mode” and enable it as well.
  5. Now move back all the way to the radar/list view to disconnect from the beacon. (Beacons stop broadcasting when connected to, so disconnecting is an important step!)

Once enabled, you can turn your beacon upside-down to turn it off, which simulates going out of range. Let’s try that: run the app on your device and put the beacon to sleep.

Important: Once the beacon is out of range, it still takes iOS about 30 seconds to truly recognize that fact. This built-in, non-adjustable delay is there to prevent false “exit” events, e.g., when a crowd temporarily obstructs the beacon and it stops being detectable for a few seconds.

Tip: The easiest way to check if we’re already “formally” outside and ready to trigger an “enter” is to lock the device, then go to the lock screen and see if the app icon is still showing. If it doesn’t, we’re good. If it does, just lock the device again, wait a few more seconds, then repeat.

Ready? Flip the beacon back, and you should see the notification show up within a few seconds.

Keep in mind: Notifications only show up if the app is not in the foreground, so make sure you’re either on the home screen, lock screen, or in another app before flipping the beacon back.

Want to try something crazy? Terminate the app by swiping it away in the app switcher. Now re-do the testing procedure: lock the phone, flip the beacon to sleep, wait 30 seconds, flip it back. Here comes our notification again. The app was—as promised—re-launched into the background to handle the “enter” event.

Key takeaways

  • Beacon monitoring allows apps to be notified whenever the device enters and exits range of a predefined set of beacons. The app will receive the event even if it’s not running at the time—it’ll be launched into the background by iOS.

  • Beacon region is a “filter” which defines which beacons to scan for. There are 3 sets of possible criteria: UUID + major + minor, UUID + major, or UUID only.

  • Use startMonitoringForRegion and stopMonitoringForRegion to control monitoring. Events are delivered to delegate methods didEnterRegion, didExitRegion and didDetermineState.

  • There’s a limit of 20 regions that can be monitored by any single app. The monitored regions persist until stopMonitoring is called or the app is uninstalled. You can access the list of all monitored regions via the monitoredRegions property of the beacon manager.

  • Testing “enter” events involves making sure you’re “outside” the region. Enabling “flip to sleep” makes it easy to simulate going out of range. “Enter” events take up to a few seconds to trigger, while “exit” events take up to 30 seconds.

Part 3: Ranging beacons