Geofencing: What do you do with it?
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.
[code lang=”objc”]
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];
}
}
];
[/code]
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
But most importantly: Simply calling -startMonitoringRegion: won’t otherwise cause iOS or your app to do anything. There are 2 more things you must do.
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:
[code lang=”objc”]
– (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
– (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
[/code]
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:
[code lang=”objc”]
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
});
}
[/code]
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.
I wrote an app to keep track of time at a location. I use it to track time to bill clients, and keep up with how often I go to the gym.
http://itunes.apple.com/us/app/onsite-time-tracker/id470803110?mt=8
Having supported the app for a while.. there are a few gotchas. It requires iOs5, but it doesn’t work on iPhone 3’s. You need an iPhone 4 or 4s. It also works much better with wifi enabled… because it’s largely using it’s crowd sourced database of wifi signals to determine your location instead of the gps, which drains your battery.
Hello,
Great post, but I was wondering what would be the difference in behaviour if in the info.plist it was missing “Required device capabilities”. Im developing a location app that runs in the background and besides some battery problems it did wake up the app while in background due to locations changes.
Was it using the GPS instead of “Location Services” even though I didnt set any accuracy or distanceFilter?
Thanks.
The “Required Device Capabilities” setting lets you identify, for the App Store really, what minimum requirements your app needs to function properly. This is so people don’t (or can’t) download it if it won’t work on their device.
So I guess strictly speaking, you don’t need it, but it’s the right thing to include if you are using location services, and it apparently imparts some wisdom to iOS about the kind of location information your app wants. If you use gps here, you are indicating both that the device needs to have GPS capability and that your app needs that kind of fine-grained accuracy. Using location-services however says that less accurate location information is OK (e.g. from WiFi and cellular network examination). In the end, its the GPS accuracy that comes at the cost of battery drain. It may very well be the case that if you do not include one of these settings, you get gps by default (if available).
I believe that any accuracy or distance filter you set in your app has no bearing on what is reported to the app while it is in the background, which supports the idea that location-services yields less accurate information and gps yields more accurate information “automatically”.
You’re a life saver. Why can’t apple docs be like this?
Thanks so much