27

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);
}
Community
  • 1
  • 1
scurioni
  • 2,344
  • 1
  • 18
  • 14
  • 3
    Is your app accepted in App Store? I'm considering to take the same approach but I need to make sure that my app won't get rejected. Thanks btw! – aslı Sep 03 '12 at 08:22
  • 1
    did you get this to work /? – SRP-Achiever Aug 19 '14 at 03:49
  • @scurioni How do you remove the status bar icon during the "cooldown" (betweeen the stopUpdatingLocation to the next startUpdatingLocation)? It stays there even after calling stopUpdatingLocation. The icon is only get disappeared after explicitly calling stopUpdatingLocation (from a ViewController or something like that) – dor506 Jan 20 '19 at 09:13

4 Answers4

10

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.

smholloway
  • 589
  • 7
  • 14
HelmiB
  • 12,303
  • 5
  • 41
  • 68
  • 4
    Where does `bgTask` come from? – SirRupertIII Jul 03 '13 at 05:17
  • 2
    property (nonatomic) UIBackgroundTaskIdentifier bgTask; in header class – HelmiB Jul 03 '13 at 06:27
  • 2
    @HelmiB: It is not working in **iOS 7**, can you suggest something else, i need to start locationmanger in background. –  Feb 05 '14 at 13:47
  • 1
    on iOS 8 you can check this method: http://stackoverflow.com/a/28373247/2102779 it should work also on iOS 7 – Leszek Szary Feb 07 '15 at 20:22
  • How do you remove the status bar icon during the "cooldown" (betweeen the stopUpdatingLocation to the next startUpdatingLocation)? It stays there even after calling stopUpdatingLocation. The icon is only get disappeared after explicitly calling stopUpdatingLocation (from a ViewController or something like that) – dor506 Jan 20 '19 at 09:13
9

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]; 
mfaani
  • 33,269
  • 19
  • 164
  • 293
scurioni
  • 2,344
  • 1
  • 18
  • 14
4

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.

jsd
  • 7,673
  • 5
  • 27
  • 47
  • 1
    and those services do not yet have a time based version, but rather, are either triggered with each update, or on significant location change – Robot Woods Apr 19 '12 at 19:25
  • But reading the post I linked, it seems people were able to do so. What do you think the difference is from what I implemented, and the code they did? – scurioni Apr 19 '12 at 19:29
  • 1
    the approach they're using (at a glance) is to use the full power background location service (so you're continually getting updates, but only handling it every X minutes). That would work, but your battery will die each night – Robot Woods Apr 19 '12 at 19:55
  • That doesn't sound about right due to this comment to a similar suggestion to yours. The comment if from the author of that post: "That way the location services are constantly enabled (indeed battery draining), I don't want that. I want to enable the location service every n minutes and immediately disable it when I have a good fix (just noticed that I didn't explain that clearly in my question). I can achieve this behaviour in the solution I described. " – scurioni Apr 19 '12 at 20:08
  • 1
    I found the discussion cited in the other post: https://devforums.apple.com/thread/146398?start=0&tstart=0 – Robot Woods Apr 19 '12 at 20:11
  • 1
    a quote "When the 10 minutes is almost up, it could call startUpdatingLocation and use the CLLocationManager Delegate callbacks (didFail.. or didUpdate...) to set up the next 10 minute background task. If the desired time interval has been reached, at this point it would connect to the server and send the necessary data. If it's, say, 40 minutes in on a 60 minute cycle, then it doesn't connect to the server and carries on setting up a new background task as normal - no problem." – Robot Woods Apr 19 '12 at 20:39
  • Thanks Robot. This discussion really helps! – scurioni Apr 19 '12 at 21:05
  • @RobotWoods Please, I can't figure out how to make this question work even though, scurioni answered how to make it work. In the post below scurioni doesn't say, where to put "those lines". Any hints? – Michal Feb 05 '13 at 16:24
  • I'm guessing the didEnterBackground method, but I haven't actually implemented this myself, it's something I need for an app I haven't begun yet – Robot Woods Feb 05 '13 at 17:37
2

Working Code(Entire Stepwise Code) Modified scurioni's code

Step 1

  • Go to project -> Capabilities -> Background Modes -> select Location updates.
  • Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with an optional string.

Step 2

Add this code to AppDelegate.m

@interface AppDelegate ()<CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSTimer *timer;
@end

Step 3 Add This Code in to applicationDidEnterBackground method in AppDelegate.m

    - (void)applicationDidEnterBackground:(UIApplication *)application {
        UIApplication *app = [UIApplication sharedApplication];
        __block UIBackgroundTaskIdentifier bgTaskId =
        [app beginBackgroundTaskWithExpirationHandler:^{
            [app endBackgroundTask:bgTaskId];
            bgTaskId = UIBackgroundTaskInvalid;
        }];

        dispatch_async( dispatch_get_main_queue(), ^{
            self.timer = nil;
            [self initTimer];
            [app endBackgroundTask:bgTaskId];
            bgTaskId = UIBackgroundTaskInvalid;
        });
    }

- (void)initTimer {
    if (nil == self.locationManager)
        self.locationManager = [[CLLocationManager alloc] init];

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

- (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];
    }
}

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation {
    NSLog(@"Did Update Location = %f / %f", [newLocation coordinate].latitude, [newLocation coordinate].longitude);
    [self updateLocationWithLatitude:[newLocation coordinate].latitude andLongitude:[newLocation coordinate].longitude];
    UIApplication*    app = [UIApplication sharedApplication];
    __block UIBackgroundTaskIdentifier bgTask =
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self initTimer];
    });
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
    [self.locationManager stopUpdatingLocation];
    UIApplication *app = [UIApplication sharedApplication];
    __block UIBackgroundTaskIdentifier bgTask =
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    [self initTimer];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Do the work associated with the task
    });
}

-(void)updateLocationWithLatitude:(CLLocationDegrees)latitude
                     andLongitude:(CLLocationDegrees)longitude{
//Here you can update your web service or back end with new latitude and longitude
}
Hari R Krishna
  • 782
  • 6
  • 20