Location Services are one of the most interesting capabilities available in iOS. One interesting feature therein is “geofencing”, also known as “region monitoring.” I recently investigated how this works and what you can do with it. While easy enough to use, region monitoring alone isn’t enough to bring your user back to your app.
The idea of geofencing is pretty simple: For a given radius around a given point, be notified when the user either enters or exits the region.
Before using region monitoring it is important to make sure the feature is even available on the device by calling the CLLocationManager class methods +regionMonitoringAvailable and +regionMonitoringEnabled.
Assuming the feature is available and enabled, it isn’t enough simply to specify a region to be monitored (but that part is easy). Here I’ve also used reverse geocoding to get a unique identifier for the region to be monitored, but any unique identifier approppriate for your app would do.
CLLocationManager *lm = <<CLLocationManger instance>>
CLLocation *loc = lm.location;
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:loc
completionHandler:^(NSArray *placemarks, NSError *error) {
if (!placemarks || placemarks.count < 1 || error) {
// handle the error condition
} else {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
CLLocationDistance distance = 200; // meters
CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:loc.coordinate radius:distance identifier:placemark.name];
[lm startMonitoringForRegion:region desiredAccuracy:kCLLocationAccuracyHundredMeters];
}
}
];
It is worth noting a few special nuances about monitored regions:
- Monitored regions are a shared system resource, so there is a (low) practical limit to the number of regions that any one device can monitor. If your app monitors too many regions, other apps won't be able to monitor regions.
- Re-using a region identifier resets that monitored region to the new CLRegion
You must indicate in your Info.plist that your app needs background location updates. That’s easy to do also. Simply add the “Required Background Modes” item:
This alerts iOS that your app should be notified, even when in the background and even if not running, of location changes, which in this case are region boundary crossings.
You must also indicate that location services are required by the app:
This is the recommended choice, unless you need fine-grained location updates, in which case you would use “gps” rather than “location-services”. But be warned, “gps” is only available on an iPhone with a GPS chip, and most importantly, this causes the GPS chip to be enabled thereby draining the battery much faster. For applications other than navigation apps, you won’t need “gps” asĀ a required device capability, and your users will thank you for not draining their battery. In my own testing, requiring “location-services” and requesting location updates, with several monitored regions showed no out of the ordinary drain on my battery.
When your app is not in the foreground, if a region boundary is crossed, iOS actually launches your app in the background and invokes the appropriate delegate method, one of:
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
Great! We’re almost there. Now what? It is in these methods that you have the opportunity to do something to get your user’s attention (or whatever you want). The key point, however, is that iOS has awakened your app briefly to handle the event. But it is enough time to do something like this:
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateActive) {
UILocalNotification *reminder = [[UILocalNotification alloc] init];
[reminder setFireDate:[NSDate date]];
[reminder setTimeZone:[NSTimeZone localTimeZone]];
[reminder setHasAction:YES];
[reminder setAlertAction:@"Show"];
[reminder setSoundName:@"bell.mp3"];
[reminder setAlertBody:@"Boundary crossed!"];
[[UIApplication sharedApplication] scheduleLocalNotification:reminder];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
// Show an alert or otherwise notify the user
});
}
This code fires a local notification if the app is not active, i.e. in the foreground, with a sound (or a vibration if the Ring/Silent switch is on Silent on an iPhone) or alternatively could show an alert to the user because the app is active and the user is presumably looking at the screen.
So all in all, it’s pretty simple and boils down to 3 things:
- Require device capability 'location-services' and request location updates in your Info.plist file
- Specify a region to monitor using CLLocationManager's -startMonitoringForRegion:
- Implement the CLLocationManager delegate methods for region boundary crossing notifications
Really cool stuff indeed. What is interesting, at least to me, is that the SDK provides so many wonderful capabilities, some of which, on their own, don’t seem useful. But, like Unix commands, if you chain them together, they become very powerful.
The three points above, by themselves, are not very exciting. But it’s the combination of asking for location updates, specifying a notifiable location event, and then acting on the notification by prompting the user in some way that makes geofencing a compelling feature in the right situations.
Have you used geofencing? What was your experience with it? Let me know in the comments.