Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Starting CLLocationManager location updates while app is in background

I'm building an app that uses CoreLocation for two purposes:

  1. First, monitoring beacon regions. Mostly we work with a specific hardware device that acts as a beacon and we track with CoreLocation the status of that beacon connection.

  2. Second, when the app detects a disconnection from the beacon device, we want to start a GPS process to locate where the disconnection happened.

In most cases, the disconnection callback of the beacon device happens while the app is in background. Therefore the app must start the GPS tracking by calling -startUpdatingLocation while being in background.

And this is the problem. iOS seems to not enable location background updates if the -startUpdatingLocation call happened in while the app was in background.

Obviously, I have configured correctly background modes, NSLocationAlwaysUsageDescription, set the property _locationManager.allowsBackgroundLocationUpdates to YES.

The amazing thing: If I call -startUpdatingLocation while being in foreground and then I switch to background, location updates are sent correctly while being in background. However, when calling -startUpdatingLocation while being in background, then the application receives 2 or 3 location updates and then pauses forever.. until I manually bring back the application to the foreground, when location updates resumes and then keep working even if I switch the app to background again.

Does anybody know or understand what should I do to fix this? or is it a major constraint of iOS? Because of the requirements of my app I really need to start locating updates while the app is in background (inside the callback of a beacon did exit region).

Btw, if you want to reproduce this issue is very easy. Just create a blank project in Xcode and add this code to your default view controller. Remember to enable the background modes add a description to your info.plist key "NSLocationAlwaysUsageDescription".

- (void)viewDidLoad 
{
    [super viewDidLoad];

    _locationManager = [[CLLocationManager alloc] init];

    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_8_3)
        _locationManager.allowsBackgroundLocationUpdates = YES;

    _locationManager.delegate = self;
    _locationManager.activityType = CLActivityTypeOther;
    _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
    _locationManager.distanceFilter = kCLDistanceFilterNone;

    [_locationManager requestAlwaysAuthorization];

    // Creating a background task to delay the start of the startUpdatingLocation
    __block UIBackgroundTaskIdentifier taskIdentifier = UIBackgroundTaskInvalid;
    taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
    }];

    // Delaying the call to startUpdatingLocation 10 seconds, enough time to dismiss the application
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [_locationManager startUpdatingLocation];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
        });
    });
}

Then, track the CLLocationManager delegate by doing some NSLogs and see what happens.

Thanks,

like image 253
vilanovi Avatar asked May 05 '16 08:05

vilanovi


1 Answers

it's been 3 months since you posted this question. Have you figured it out on your own already?

I tried out your code. I put some NSLogs in the AppDelegate delegate functions, and in the viewDidLoad. Also, in the AppDelegate class, I logged out the background time left.

If startUpdatingLocation is called before going into the background:

2016-08-18 01:22:52.355 test background GPS from StackOF[2267:745042] viewDidLoad
2016-08-18 01:22:52.376 test background GPS from StackOF[2267:745042] _locationManager.allowsBackgroundLocationUpdates = YES;
2016-08-18 01:22:52.382 test background GPS from StackOF[2267:745042] _locationManager requestAlwaysAuthorization
2016-08-18 01:22:52.410 test background GPS from StackOF[2267:745042] applicationDidBecomeActive
2016-08-18 01:22:52.469 test background GPS from StackOF[2267:745042] viewDidAppear
2016-08-18 01:23:03.361 test background GPS from StackOF[2267:745042] [_locationManager startUpdatingLocation];
2016-08-18 01:23:03.362 test background GPS from StackOF[2267:745042] start updating location, _locationManager.allowsBackgroundLocationUpdates == YES
2016-08-18 01:23:03.382 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 65.000000
2016-08-18 01:23:03.630 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 1743.000000
2016-08-18 01:23:03.709 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 1743.000000
2016-08-18 01:23:03.716 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 1743.000000
2016-08-18 01:23:03.720 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 1743.000000
2016-08-18 01:23:03.814 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 65.000000
2016-08-18 01:23:05.004 test background GPS from StackOF[2267:745042] inner block [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
2016-08-18 01:23:05.005 test background GPS from StackOF[2267:745042] inner block start updating location, _locationManager.allowsBackgroundLocationUpdates == YES
2016-08-18 01:23:08.287 test background GPS from StackOF[2267:745042] applicationDidEnterBackground. Background time left: 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000
2016-08-18 01:23:10.319 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 65.000000
2016-08-18 01:23:19.321 test background GPS from StackOF[2267:745042] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 65.000000
/****
Keep on polling...
****/

If startUpdatingLocation is called after going into the background:

2016-08-18 01:34:29.023 test background GPS from StackOF[2285:747132] viewDidLoad
2016-08-18 01:34:29.030 test background GPS from StackOF[2285:747132] _locationManager.allowsBackgroundLocationUpdates = YES;
2016-08-18 01:34:29.033 test background GPS from StackOF[2285:747132] _locationManager requestAlwaysAuthorization
2016-08-18 01:34:29.047 test background GPS from StackOF[2285:747132] applicationDidBecomeActive
2016-08-18 01:34:29.104 test background GPS from StackOF[2285:747132] viewDidAppear
2016-08-18 01:34:31.612 test background GPS from StackOF[2285:747132] applicationDidEnterBackground. Background time left: 179.964144
2016-08-18 01:34:40.004 test background GPS from StackOF[2285:747132] [_locationManager startUpdatingLocation];
2016-08-18 01:34:40.006 test background GPS from StackOF[2285:747132] start updating location, _locationManager.allowsBackgroundLocationUpdates == YES
2016-08-18 01:34:40.290 test background GPS from StackOF[2285:747132] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 10.000000
2016-08-18 01:34:40.332 test background GPS from StackOF[2285:747132] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 355.117789
2016-08-18 01:34:40.336 test background GPS from StackOF[2285:747132] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 355.118536
2016-08-18 01:34:40.493 test background GPS from StackOF[2285:747132] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 355.141209
2016-08-18 01:34:40.496 test background GPS from StackOF[2285:747132] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 355.147748
2016-08-18 01:34:40.505 test background GPS from StackOF[2285:747132] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 355.148158
2016-08-18 01:34:40.507 test background GPS from StackOF[2285:747132] Lat/Long: <redacted GPS coordinates>, horizontal accuracy: 65.000000
2016-08-18 01:34:41.640 test background GPS from StackOF[2285:747132] inner block [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
2016-08-18 01:34:41.642 test background GPS from StackOF[2285:747132] inner block start updating location, _locationManager.allowsBackgroundLocationUpdates == YES
/****
stops polling...
****/

Take note of the background time left. If the app goes into the background before the CLLocationManager starts updating location, the background time left is 3 minutes. I'm not too sure myself as I too have my own GPS background running issue, but from this brief test it seems like the CLLocationManager must have been called before the app goes into the background.

Here are where I put my NSLogs:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSLog(@"viewDidLoad");

    _locationManager = [[CLLocationManager alloc] init];

    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_8_3)
    {
        _locationManager.allowsBackgroundLocationUpdates = YES;
        NSLog(@"_locationManager.allowsBackgroundLocationUpdates = YES;");
    }



    _locationManager.delegate = self;
    _locationManager.activityType = CLActivityTypeOther;
    _locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
    _locationManager.distanceFilter = kCLDistanceFilterNone;

    [_locationManager requestAlwaysAuthorization];
    NSLog(@"_locationManager requestAlwaysAuthorization");

    // Creating a background task to delay the start of the startUpdatingLocation
    __block UIBackgroundTaskIdentifier taskIdentifier = UIBackgroundTaskInvalid;
    taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
        NSLog(@"[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];");
    }];

    // Delaying the call to startUpdatingLocation 10 seconds, enough time to dismiss the application
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [_locationManager startUpdatingLocation];
        NSLog(@"[_locationManager startUpdatingLocation];");
        if(_locationManager.allowsBackgroundLocationUpdates == YES)
        {
            NSLog(@"start updating location, _locationManager.allowsBackgroundLocationUpdates == YES");
        }
        else
        {
            NSLog(@"start updating location, _locationManager.allowsBackgroundLocationUpdates == NO");
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
            NSLog(@"inner block [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];");
            if(_locationManager.allowsBackgroundLocationUpdates == YES)
            {
                NSLog(@"inner block start updating location, _locationManager.allowsBackgroundLocationUpdates == YES");
            }
            else
            {
                NSLog(@"inner block start updating location, _locationManager.allowsBackgroundLocationUpdates == NO");
            }

        });
    });

}

The CLLocationManager delegate function:

-(void)locationManager:(CLLocationManager *) manager didUpdateLocations:(nonnull NSArray<CLLocation *> *)locations
{
    CLLocation *location = [locations firstObject];
    NSLog(@"Lat/Long: %f %f, horizontal accuracy: %f",
          location.coordinate.latitude,
          location.coordinate.longitude,
          location.horizontalAccuracy);
}

The AppDelegate:

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

    NSLog(@"applicationDidEnterBackground. Background time left: %f", [[UIApplication sharedApplication] backgroundTimeRemaining]);

}

TL;DR: try calling startUpdatingLocation right in viewDidLoad or in the AppDelegate application:didFinishLaunchingWithOptions:

like image 80
GameBoy Avatar answered Oct 16 '22 19:10

GameBoy