Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Start Location Manager in iOS 7 from background task

It seems that in iOS 7 an app can not start Location Manager (by calling startUpdatingLocation) from the background task anymore.

In iOS 6 I used approach described here: https://stackoverflow.com/a/6465280 to run background location update every n minutes. The idea was to run background task with a timer and start Location Manager when the timer triggers it. After that turn off Location Manager and start another background task.

After updating to iOS 7 this approach does not work anymore. After starting Location Manager an app does not receive any locationManager:didUpdateLocations. Any ideas?

like image 323
sash Avatar asked Sep 19 '13 17:09

sash


2 Answers

I found the problem/solution. When it is time to start location service and stop background task, background task should be stopped with a delay (I used 1 second). Otherwise location service wont start. Also Location Service should be left ON for a couple of seconds (in my example it is 3 seconds).

Another important notice, max background time in iOS 7 is now 3 minutes instead of 10 minutes.

Updated on October 29 '16

There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.

let manager = APScheduledLocationManager(delegate: self) manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100) 

The repository also contains an example app written in Swift 3.

Updated on May 27 '14

Objective-C example:

1) In ".plist" file set UIBackgroundModes to "location".

2) Create instance of ScheduledLocationManager anywhere you want.

@property (strong, nonatomic) ScheduledLocationManager *slm; 

3) Set it up

self.slm = [[ScheduledLocationManager alloc]init]; self.slm.delegate = self; [self.slm getUserLocationWithInterval:60]; // replace this value with what you want, but it can not be higher than kMaxBGTime 

4) Implement delegate methods

-(void)scheduledLocationManageDidFailWithError:(NSError *)error {     NSLog(@"Error %@",error); }  -(void)scheduledLocationManageDidUpdateLocations:(NSArray *)locations {     // You will receive location updates every 60 seconds (value what you set with getUserLocationWithInterval)     // and you will continue to receive location updates for 3 seconds (value of kTimeToGetLocations).     // You can gather and pick most accurate location     NSLog(@"Locations %@",locations); } 

Here is implementation of ScheduledLocationManager:

ScheduledLocationManager.h

#import <Foundation/Foundation.h> #import <CoreLocation/CoreLocation.h>  @protocol ScheduledLocationManagerDelegate <NSObject>  -(void)scheduledLocationManageDidFailWithError:(NSError*)error; -(void)scheduledLocationManageDidUpdateLocations:(NSArray*)locations;  @end  @interface ScheduledLocationManager : NSObject <CLLocationManagerDelegate>  -(void)getUserLocationWithInterval:(int)interval;  @end 

ScheduledLocationManager.m

#import "ScheduledLocationManager.h"  int const kMaxBGTime = 170; // 3 min - 10 seconds (as bg task is killed faster) int const kTimeToGetLocations = 3; // time to wait for locations  @implementation ScheduledLocationManager {     UIBackgroundTaskIdentifier bgTask;     CLLocationManager *locationManager;     NSTimer *checkLocationTimer;     int checkLocationInterval;     NSTimer *waitForLocationUpdatesTimer; }  - (id)init {     self = [super init];     if (self) {         locationManager = [[CLLocationManager alloc] init];         locationManager.delegate = self;         locationManager.desiredAccuracy = kCLLocationAccuracyBest;         locationManager.distanceFilter = kCLDistanceFilterNone;          [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];     }     return self; }  -(void)getUserLocationWithInterval:(int)interval {     checkLocationInterval = (interval > kMaxBGTime)? kMaxBGTime : interval;     [locationManager startUpdatingLocation]; }  - (void)timerEvent:(NSTimer*)theTimer {     [self stopCheckLocationTimer];     [locationManager startUpdatingLocation];      // in iOS 7 we need to stop background task with delay, otherwise location service won't start     [self performSelector:@selector(stopBackgroundTask) withObject:nil afterDelay:1]; }  -(void)startCheckLocationTimer {     [self stopCheckLocationTimer];     checkLocationTimer = [NSTimer scheduledTimerWithTimeInterval:checkLocationInterval target:self selector:@selector(timerEvent:) userInfo:NULL repeats:NO]; }  -(void)stopCheckLocationTimer {     if(checkLocationTimer){         [checkLocationTimer invalidate];         checkLocationTimer=nil;     } }  -(void)startBackgroundTask {     [self stopBackgroundTask];     bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{         //in case bg task is killed faster than expected, try to start Location Service         [self timerEvent:checkLocationTimer];     }]; }  -(void)stopBackgroundTask {     if(bgTask!=UIBackgroundTaskInvalid){         [[UIApplication sharedApplication] endBackgroundTask:bgTask];         bgTask = UIBackgroundTaskInvalid;     } }  -(void)stopWaitForLocationUpdatesTimer {     if(waitForLocationUpdatesTimer){         [waitForLocationUpdatesTimer invalidate];         waitForLocationUpdatesTimer =nil;     } }  -(void)startWaitForLocationUpdatesTimer {     [self stopWaitForLocationUpdatesTimer];     waitForLocationUpdatesTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeToGetLocations target:self selector:@selector(waitForLoactions:) userInfo:NULL repeats:NO]; }  - (void)waitForLoactions:(NSTimer*)theTimer {     [self stopWaitForLocationUpdatesTimer];      if(([[UIApplication sharedApplication ]applicationState]==UIApplicationStateBackground ||         [[UIApplication sharedApplication ]applicationState]==UIApplicationStateInactive) &&        bgTask==UIBackgroundTaskInvalid){         [self startBackgroundTask];     }      [self startCheckLocationTimer];     [locationManager stopUpdatingLocation]; }  #pragma mark - CLLocationManagerDelegate methods  - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {     if(checkLocationTimer){         //sometimes it happens that location manager does not stop even after stopUpdationLocations         return;     }      if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidUpdateLocations:)]) {         [self.delegate scheduledLocationManageDidUpdateLocations:locations];     }      if(waitForLocationUpdatesTimer==nil){         [self startWaitForLocationUpdatesTimer];     } }  - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {     if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidFailWithError:)]) {         [self.delegate scheduledLocationManageDidFailWithError:error];     } }  #pragma mark - UIAplicatin notifications  - (void)applicationDidEnterBackground:(NSNotification *) notification {     if([self isLocationServiceAvailable]==YES){         [self startBackgroundTask];     } }  - (void)applicationDidBecomeActive:(NSNotification *) notification {     [self stopBackgroundTask];     if([self isLocationServiceAvailable]==NO){         NSError *error = [NSError errorWithDomain:@"your.domain" code:1 userInfo:[NSDictionary dictionaryWithObject:@"Authorization status denied" forKey:NSLocalizedDescriptionKey]];          if (self.delegate && [self.delegate respondsToSelector:@selector(scheduledLocationManageDidFailWithError:)]) {             [self.delegate scheduledLocationManageDidFailWithError:error];         }     } }  #pragma mark - Helpers  -(BOOL)isLocationServiceAvailable {     if([CLLocationManager locationServicesEnabled]==NO ||        [CLLocationManager authorizationStatus]==kCLAuthorizationStatusDenied ||        [CLLocationManager authorizationStatus]==kCLAuthorizationStatusRestricted){         return NO;     }else{         return YES;     } }  @end 
like image 90
sash Avatar answered Oct 12 '22 12:10

sash


I tried your method but it didn't work on my side. Can you show me your code?

I actually found a solution to solve the location service problem in iOS 7.

In iOS 7, you can not start the location service in background. If you want the location service to keep running in the background, you have to start it in foreground and it will continue to run in the background.

If you were like me, stop the location service and use timer to re-start it in the background, it will NOT work in iOS 7.

For more detailed information, you can watch the first 8 minutes of video 307 from WWDC 2013: https://developer.apple.com/wwdc/videos/

Update: The location service can work in background as well. Please check Background Location Services not working in iOS 7 for the updated post with complete solution posted on Github and a blog post explaining the details.

like image 25
Ricky Avatar answered Oct 12 '22 11:10

Ricky