How do I get a background location update every n minutes in my iOS application?
Solution 1:
I found a solution to implement this with the help of the Apple Developer Forums:
- Specify
location background mode
- Create an
NSTimer
in the background withUIApplication:beginBackgroundTaskWithExpirationHandler:
- When
n
is smaller thanUIApplication:backgroundTimeRemaining
it will work just fine. Whenn
is larger, thelocation manager
should be enabled (and disabled) again before there is no time remaining to avoid the background task being killed.
This works because location is one of the three allowed types of background execution.
Note: I lost some time by testing this in the simulator where it doesn't work. However, it works fine on my phone.
Solution 2:
On iOS 8/9/10 to make background location update every 5 minutes do the following:
Go to Project -> Capabilities -> Background Modes -> select Location updates
Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with empty value (or optionally any text)
To make location working when your app is in the background and send coordinates to web service or do anything with them every 5 minutes implement it like in the code below.
I'm not using any background tasks or timers. I've tested this code with my device with iOS 8.1 which was lying on my desk for few hours with my app running in the background. Device was locked and the code was running properly all the time.
@interface LocationManager () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSDate *lastTimestamp;
@end
@implementation LocationManager
+ (instancetype)sharedInstance
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
LocationManager *instance = sharedInstance;
instance.locationManager = [CLLocationManager new];
instance.locationManager.delegate = instance;
instance.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // you can use kCLLocationAccuracyHundredMeters to get better battery life
instance.locationManager.pausesLocationUpdatesAutomatically = NO; // this is important
});
return sharedInstance;
}
- (void)startUpdatingLocation
{
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusDenied)
{
NSLog(@"Location services are disabled in settings.");
}
else
{
// for iOS 8
if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
{
[self.locationManager requestAlwaysAuthorization];
}
// for iOS 9
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
{
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
[self.locationManager startUpdatingLocation];
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *mostRecentLocation = locations.lastObject;
NSLog(@"Current location: %@ %@", @(mostRecentLocation.coordinate.latitude), @(mostRecentLocation.coordinate.longitude));
NSDate *now = [NSDate date];
NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;
if (!self.lastTimestamp || interval >= 5 * 60)
{
self.lastTimestamp = now;
NSLog(@"Sending current location to web service.");
}
}
@end
Solution 3:
I did this in an application I'm developing. The timers don't work when the app is in the background but the app is constantly receiving the location updates. I read somewhere in the documentation (i can't seem to find it now, i'll post an update when i do) that a method can be called only on an active run loop when the app is in the background. The app delegate has an active run loop even in the bg so you dont need to create your own to make this work. [Im not sure if this is the correct explanation but thats how I understood from what i read]
First of all, add the location
object for the key UIBackgroundModes
in your app's info.plist. Now, what you need to do is start the location updates anywhere in your app:
CLLocationManager locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;//or whatever class you have for managing location
[locationManager startUpdatingLocation];
Next, write a method to handle the location updates,
say -(void)didUpdateToLocation:(CLLocation*)location
, in the app delegate. Then implement the method locationManager:didUpdateLocation:fromLocation
of CLLocationManagerDelegate
in the class in which you started the location manager (since we set the location manager delegate to 'self'). Inside this method you need to check if the time interval after which you have to handle the location updates has elapsed. You can do this by saving the current time every time. If that time has elapsed, call the method UpdateLocation from your app delegate:
NSDate *newLocationTimestamp = newLocation.timestamp;
NSDate *lastLocationUpdateTiemstamp;
int locationUpdateInterval = 300;//5 mins
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (userDefaults) {
lastLocationUpdateTiemstamp = [userDefaults objectForKey:kLastLocationUpdateTimestamp];
if (!([newLocationTimestamp timeIntervalSinceDate:lastLocationUpdateTiemstamp] < locationUpdateInterval)) {
//NSLog(@"New Location: %@", newLocation);
[(AppDelegate*)[UIApplication sharedApplication].delegate didUpdateToLocation:newLocation];
[userDefaults setObject:newLocationTimestamp forKey:kLastLocationUpdateTimestamp];
}
}
}
This will call your method every 5 mins even when your app is in background.
Imp: This implementation drains the battery, if your location data's accuracy is not critical you should use [locationManager startMonitoringSignificantLocationChanges]
Before adding this to your app, please read the Location Awareness Programming Guide