Recieving location updates after app is terminated

cllocationcllocationmanager

I need to keep track of the user location all the time (but not drain the battery).
I understand the only way to get updates after app is terminated is using startMonitoringSignificantLocationChanges.

From Apple's Location Awareness Programming Guide on startMonitoringSignificantLocationChanges:

If you start this service and your application is subsequently
terminated, the system automatically relaunches the application into
the background if a new event arrives. In such a case, the options
dictionary passed to the application:didFinishLaunchingWithOptions:
method of your application delegate contains the key
UIApplicationLaunchOptionsLocationKey to indicate that your
application was launched because of a location event. Upon relaunch,
you must still configure a location manager object and call this
method to continue receiving location events. When you restart
location services, the current event is delivered to your delegate
immediately. In addition, the location property of your location
manager object is populated with the most recent location object even
before you start location services.

I would be glad if someone could demonstrate in the code (give an example) which methods i should use

In the following code i'm tring to :
– start the location manager at appdelegate which strats the signinficant monitor changes update and startupdating.
– in didUpdateToLocation i'm calling stopupdating
– in didFinishLaunchingWithOptions when i check if i got a UIApplicationLaunchOptionsLocationKey in order to know if i'm in the background and launched due to siginificant monitor location update.
– if so, i call startMonitoringSignificantLocationChanges again (not sure why…)
and begin a UIBackgeoundTaskIdentifier for calling startupdating method.

LocationController.m : 
+ (LocationController*)sharedInstance {
    @synchronized(self) {
        if (sharedCLDelegate == nil) {
            [[self alloc] init];
        }
    }
    return sharedCLDelegate;
}

- (id)init
{
    self = [super init];
    if (self != nil) {
        self.locationManager = [[[CLLocationManager alloc] init] autorelease];
        self.locationManager.delegate = self;
        self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
        [self.locationManager startUpdatingLocation];
        [self.locationManager startMonitoringSignificantLocationChanges];

    }
    return self;
}
- (void) startMonitoringSignificantLocationChanges
{
    [self.locationManager startMonitoringSignificantLocationChanges];
}
- (void) stopMonitoringSignificantLocationChanges
{
    [self.locationManager stopMonitoringSignificantLocationChanges];
}
-(void) start{
    [self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation{
    if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 30) {
        self.lastLocation = newLocation;
        [self updateLocation]; //sending location to server
        [self.locationManager stopUpdatingLocation];
    }
}
- (void)locationManager:(CLLocationManager*)manager
       didFailWithError:(NSError*)error{
    [self.locationManager stopUpdatingLocation];
}

AppDelegate.h : 

@interface AppDelegate : NSObject <UIApplicationDelegate> {
    UIBackgroundTaskIdentifier bgTask;
}

AppDelegate.m : 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
        id locationValue = [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey];
        if (locationValue) {
            [[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
            UIApplication *app  = [UIApplication sharedApplication];
            bgTask = [app beginBackgroundTaskWithExpirationHandler:^{ 
                [app endBackgroundTask:bgTask]; 
                bgTask = UIBackgroundTaskInvalid;
            }];
            [[LocationController sharedInstance] start]; //startupdating
            return YES;
        }
    else { 
            [[LocationController sharedInstance] init];
    }
}
-(void) applicationDidEnterBackground:(UIApplication *) application
{
    NSLog(@"entered background Mode");
}

-(void) applicationDidBecomeActive:(UIApplication *) application
{
    NSLog(@"application Did Become Active");
}

Thank you.

Best Answer

Using your classes, this is what I would do.

In your AppDelegate.m, when your app is in the foreground or background, I'd move the CLLocationManager to run in the foreground / background to match. The reason I'm doing this is because if the CLLocationManager is not moved to the background when the app is in the background, no location updates are sent to the CLLocationManager's callback

- (void)applicationDidEnterBackground:(UIApplication *) application {
    [[LocationController sharedInstance] stop];
    [[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
    NSLog(@"entered background Mode");
}

- (void)applicationDidBecomeActive:(UIApplication *) application {
    [[LocationController sharedInstance] stopMonitoringSignificantLocationChanges];
    [[LocationController sharedInstance] start];
    NSLog(@"application Did Become Active");
}

So lets say your app then moves to the background, and after awhile, iOS decides it's using too much memory and kills your app.

A few minutes later, iOS then receives a location update, and respawns your app, letting it know it respawned due to a location service. You then need to re-start the background location service, as it will be the only chance your app has to do so.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
        [[LocationController sharedInstance] startMonitoringSignificantLocationChanges];
    }
    return YES;
}

Oh, and one last change, I'm not sure why in your locationManager:didUpdateToLocation:fromLocation: method you're stopping the location service, as when you do that no more updates come through. Just leave it running, then every time a location change comes through you can send that to the server.

- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {

    if ( abs([newLocation.timestamp timeIntervalSinceDate: [NSDate date]]) < 30) {
        self.lastLocation = newLocation;
        [self updateLocation]; //sending location to server

}
Related Topic