Is it possible to make use of geo-based push notifications on iOS when the application is killed (not in the background)?
I am interested in building an app, where the user will choose a position on a map, and then if he/she for example is close to that area a local geo-based push notification would be triggered.
However is this "idea" even possible? Can the GPS run and compare coordinates when the app is killed and run and notify the user when is in place? Is there a tutorial/article/more information of any kind on the subject that i could read?
Most of the information I read online were more like general ideas of implementing without anything specific though on the matter.
For tracking a user's location while the app is not running (ie. has previously been terminated), there are two options:
From the iOS app programming guide under "Tracking the User's Location":
The significant-change location service is highly recommended for apps that do not need high-precision location data. With this service, location updates are generated only when the user’s location changes significantly; thus, it is ideal for social apps or apps that provide the user with noncritical, location-relevant information. If the app is suspended when an update occurs, the system wakes it up in the background to handle the update. If the app starts this service and is then terminated, the system relaunches the app automatically when a new location becomes available. This service is available in iOS 4 and later, and it is available only on devices that contain a cellular radio.
However, according to the CLLocationManager class reference, it's not too accurate and updates are infrequent:
Note: Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes. If the device is able to retrieve data from the network, the location manager is much more likely to deliver notifications in a timely manner.
Region Monitoring works in a similar way - including restarting the app after being terminated - but with higher accuracy (depending on availability of Wifi networks and cell towers):
The specific threshold distances are determined by the hardware and the location technologies that are currently available. For example, if Wi-Fi is disabled, region monitoring is significantly less accurate. However, for testing purposes, you can assume that the minimum distance is approximately 200 meters.
Another region monitoring consideration is that (according to the CLLocationManager class reference) region entry and exit notifications might only be received 3-5 minutes or so after crossing the region's boundaries.
Depending on the actual requirements, region monitoring could be used for obtaining a "rough" location and then when the user is within a specific region, start up the more accurate GPS based service on the location manager. When the user leaves the region of interest, turn off the GPS service to preserve battery and revert to the rough location monitoring service (ie. region monitoring) once again. Here's a basic implementation:
SomeViewController.m:
...
@interface SomeViewController () <CLLocationManagerDelegate>
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) CLRegion *someRegion;
@end
@implementation SomeViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
CLLocationDistance radius = 10; // 10 metre sensitivity
self.someRegion = [[CLRegion alloc] initCircularRegionWithCenter:someCoordinates radius:radius identifier:@"Smithtown Dry Cleaners"];
self.locationManager.delegate = self;
[self.locationManager startMonitoringForRegion:self.someRegion];
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 10;
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
[self.locationManager stopUpdatingLocation];
}
// Delegate method from the CLLocationManagerDelegate protocol.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* location = [locations lastObject];
// If the user's current location is not within the region anymore, stop updating
if ([self.someRegion containsCoordinate:location.coordinate] == NO) {
[self.locationManager stopUpdatingLocation];
}
NSString *locationData = [NSString stringWithFormat:@"latitude %+.6f, longitude %+.6f\n",
location.coordinate.latitude,
location.coordinate.longitude];
NSLog(@"%@", locationData);
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = locationData;
localNotification.alertAction = @"Location data received";
localNotification.hasAction = YES;
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
}
Remember to add the appropriate entries to the application's plist file so the app will run in the background with access to the appropriate resources:
MyApp-Info.plist:
<key>UIBackgroundModes</key>
<array>
...
<string>location</string>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
...
<string>location-services</string>
<string>gps</string>
</array>
The above code assumes the use of iOS6 and ARC