Getting user location every n minutes after app goes to background

I am trying to implement the suggestions given in this post.

Unfortunately the steps are not clear to me. I tried implementing those suggestions, but the backgroundTimeRemaining continues to decrease even after I start and stop locationServices. This is how I developed it:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    UIApplication*    app = [UIApplication sharedApplication];

    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // Do the work associated with the task.
        self.timer = nil;
        [self initTimer];

    });
}

initTimer:

- (void)initTimer {

    // Create the location manager if this object does not
    // already have one.
    if (nil == self.locationManager)
        self.locationManager = [[CLLocationManager alloc] init];

    self.locationManager.delegate = self;
    [self.locationManager startMonitoringSignificantLocationChanges];

    if (self.timer == nil) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3
                                              target:self
                                            selector:@selector(checkUpdates:)
                                            userInfo:nil
                                             repeats:YES];
    }
}

checkUpdates:

- (void)checkUpdates:(NSTimer *)timer{
    UIApplication*    app = [UIApplication sharedApplication];
    double remaining = app.backgroundTimeRemaining;
    if(remaining < 580.0) {
        [self.locationManager startUpdatingLocation]; 
        [self.locationManager stopUpdatingLocation]; 
        [self.locationManager startMonitoringSignificantLocationChanges];
    }
    DbgLog(@"Reminaing %f", app.backgroundTimeRemaining);
}

Does anyone have a suggestion on what might be wrong in my code? Both initTimer and checkUpdates are being called, but only during for the background execution time (+- 10 Mins). I want the app to update the location every n minutes "forever".

My app's UIBackgroundModes is set to location.

UPDATE:

I am now resetting the timer on didUpdateToLocation and didFailWithError. But still the backgroundTimeRemaining keeps decreasing:

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

NSLog(@"Did Update Location = %f / %f", [newLocation coordinate].latitude, [newLocation coordinate].longitude);

UIApplication*    app = [UIApplication sharedApplication];

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    [app endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task.

    [self initTimer];

});
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {

[self.locationManager stopUpdatingLocation]; 
UIApplication*    app = [UIApplication sharedApplication];

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    [app endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
[self initTimer];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task.

});

}

I am also invalidating the timer:

- (void)checkUpdates:(NSTimer *)timer{
UIApplication*    app = [UIApplication sharedApplication];
double remaining = app.backgroundTimeRemaining;
if(remaining < 580.0 && remaining > 570.0) {
    [self.timer invalidate];
    self.timer = nil;
    [self.locationManager startUpdatingLocation]; 
    [self.locationManager stopUpdatingLocation]; 
}
DbgLog(@"*************************Checking for updates!!!!!!!!!!! Reminaing %f", app.backgroundTimeRemaining);
}

Solution 1:

To anyone else having a nightmare of a time trying to figure this one out, I have a simple solution.

  1. Study the example from raywenderlich.com. The sample code works perfectly, but unfortunately there's no timer during background location. This will run indefinitely.
  2. Add timer by using this code snippet:

    -(void)applicationDidEnterBackground {
      [self.locationManager stopUpdatingLocation];
    
      UIApplication* app = [UIApplication sharedApplication];
    
      bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
      }];
    
      self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
                                                    target:self.locationManager
                                                  selector:@selector(startUpdatingLocation)
                                                  userInfo:nil
                                                   repeats:YES];
    }
    
  3. Just don't forget to add "App registers for location updates" in info.plist.

Solution 2:

After some days trying all possible solutions I was finally able to make it work. Here is what was wrong in my solution:

  • I need to setup 2 UIBackgroundTaskIdentifiers, one for the timer, and another one for the locationManager

On my solution, just add:

UIApplication *app = [UIApplication sharedApplication];

bgTask2 = [app beginBackgroundTaskWithExpirationHandler:^{ 
[app endBackgroundTask:bgTask2]; 
bgTask2 = UIBackgroundTaskInvalid; }];

self.locationManager.delegate = self; 
[self.locationManager startUpdatingLocation]; 

Solution 3:

Once you're in the background you can only have 10 minutes of additional time. There is no way to extend that. You should use the location background services instead.