user3573457
user3573457

Reputation: 115

iOS 9 how get locations even if app terminated

I understand how retrieve locations in background. And I understand that there is a chance to get locations even if terminated Continious location updates even if app is terminated in iOS

But. I have app Moves and Foursquare. If this app even not running (I terminate all apps and no apps running) and then I go to 'Privacy' and change for this apps locations to disable (Never), I can see that arrow in status bar disappeared. But when I enable location updates (Always), arrow again appeared in status bar and apps not running in this moment. So this apps begin take info about locations. How? Even If not launch MOves for some days, this app then show me right route for past days. How they retrieve location info about past days, even app not launching?

Upvotes: 3

Views: 5470

Answers (2)

bharathi kumar
bharathi kumar

Reputation: 208

i jus followed @Busata's answer and it worked well with iOS 12.

Here i will post the answer in Swift.

LocationManager Class:

class NABackgroundRideLocationTrackingManager: 
NSObject,CLLocationManagerDelegate {
    var locationManager: CLLocationManager?
    private static var privateShared : 
NABackgroundRideLocationTrackingManager?
    var traveledDistance: Double = 0
    class func shared() -> NABackgroundRideLocationTrackingManager {
    guard let uwShared = privateShared else {
        privateShared = NABackgroundRideLocationTrackingManager()
        return privateShared!
    }
    return uwShared
}
class func destroy() {
    privateShared = nil
}

func startMonitoringLocation(){
    if locationManager != nil{
        locationManager?.stopMonitoringSignificantLocationChanges()
    }
    locationManager = CLLocationManager()
    locationManager?.delegate = self
    locationManager?.allowsBackgroundLocationUpdates = true
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    locationManager?.activityType = CLActivityType.otherNavigation
    locationManager?.requestAlwaysAuthorization()
    locationManager?.startMonitoringSignificantLocationChanges()
}
func restartMonitoringLocation(){
    locationManager?.stopMonitoringSignificantLocationChanges()
    locationManager?.requestAlwaysAuthorization()
    locationManager?.startMonitoringSignificantLocationChanges()
}
func postLocations(userLocation: CLLocation){
    var params: [String:String] = [:]
    params["latitude"] = String(userLocation.coordinate.latitude)
    params["longitude"] = String(userLocation.coordinate.longitude)
    NAAppManager.postLocationOnBackground(params, success: { (success) in
        print("location Posted")
    }) { (failure) in
        print("Failed")
    }
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
    let location = locations.last
    self.postLocations(userLocation: location!)
}
}


Appdelegate:

class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    if UIApplication.shared.backgroundRefreshStatus == .restricted {

    }else if UIApplication.shared.backgroundRefreshStatus == .denied {

    }else{
        if ((launchOptions?[UIApplication.LaunchOptionsKey.location]) != nil){
            NABackgroundRideLocationTrackingManager.shared().startMonitoringLocation()
        }
    }

}
func applicationDidEnterBackground(_ application: UIApplication) {
    NABackgroundRideLocationTrackingManager.shared().restartMonitoringLocation()

}
func applicationDidBecomeActive(_ application: UIApplication) {
    NABackgroundRideLocationTrackingManager.shared().startMonitoringLocation()
}
}

Upvotes: 0

Busata
Busata

Reputation: 1039

I found a solutions for this. This worked for me in IOS 9. Even restarting the IOS device, keeps running.

http://mobileoop.com/getting-location-updates-for-ios-7-and-8-when-the-app-is-killedterminatedsuspended

The GitHub sample: https://github.com/voyage11/GettingLocationWhenSuspended

UPDATED

You must use [myLocationManager startMonitoringSignificantLocationChanges], and not [myLocationManager startUpdatingLocation].

Below, the my AppDelegate.

@implementation LocationAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"didFinishLaunchingWithOptions");

    self.shareModel = [LocationManager sharedManager];
    self.shareModel.afterResume = NO;

    [self.shareModel addApplicationStatusToPList:@"didFinishLaunchingWithOptions"];

     UIAlertView * alert;

    //We have to make sure that the Background App Refresh is enable for the Location updates to work in the background.
    if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusDenied) {

        alert = [[UIAlertView alloc]initWithTitle:@""
                                          message:@"The app doesn't work without the Background App Refresh enabled. To turn it on, go to Settings > General > Background App Refresh"
                                         delegate:nil
                                cancelButtonTitle:@"Ok"
                                otherButtonTitles:nil, nil];
        [alert show];

    } else if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted) {

        alert = [[UIAlertView alloc]initWithTitle:@""
                                          message:@"The functions of this app are limited because the Background App Refresh is disable."
                                         delegate:nil
                                cancelButtonTitle:@"Ok"
                                otherButtonTitles:nil, nil];
        [alert show];

    } else {

        // When there is a significant changes of the location,
        // The key UIApplicationLaunchOptionsLocationKey will be returned from didFinishLaunchingWithOptions
        // When the app is receiving the key, it must reinitiate the locationManager and get
        // the latest location updates

        // This UIApplicationLaunchOptionsLocationKey key enables the location update even when
        // the app has been killed/terminated (Not in th background) by iOS or the user.

        NSLog(@"UIApplicationLaunchOptionsLocationKey : %@" , [launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]);
        if ([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {

            // This "afterResume" flag is just to show that he receiving location updates
            // are actually from the key "UIApplicationLaunchOptionsLocationKey"
            self.shareModel.afterResume = YES;

            [self.shareModel startMonitoringLocation];
            [self.shareModel addResumeLocationToPList];
        }
    }

    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"applicationDidEnterBackground");
    [self.shareModel restartMonitoringLocation];

    [self.shareModel addApplicationStatusToPList:@"applicationDidEnterBackground"];
}



- (void)applicationDidBecomeActive:(UIApplication *)application {
    NSLog(@"applicationDidBecomeActive");

    [self.shareModel addApplicationStatusToPList:@"applicationDidBecomeActive"];

    //Remove the "afterResume" Flag after the app is active again.
    self.shareModel.afterResume = NO;

    [self.shareModel startMonitoringLocation];
}


- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"applicationWillTerminate");
    [self.shareModel addApplicationStatusToPList:@"applicationWillTerminate"];
}


@end

My custom LocationManager.

@implementation LocationManager

//Class method to make sure the share model is synch across the app
+ (id)sharedManager {
    static id sharedMyModel = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        sharedMyModel = [[self alloc] init];
    });

    return sharedMyModel;
}


#pragma mark - CLLocationManager

- (void)startMonitoringLocation {
    if (_anotherLocationManager)
        [_anotherLocationManager stopMonitoringSignificantLocationChanges];

    self.anotherLocationManager = [[CLLocationManager alloc]init];
    _anotherLocationManager.delegate = self;
    _anotherLocationManager.allowsBackgroundLocationUpdates = true;
    _anotherLocationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
    _anotherLocationManager.activityType = CLActivityTypeOtherNavigation;

    if(IS_OS_8_OR_LATER) {
        [_anotherLocationManager requestAlwaysAuthorization];
    }
    [_anotherLocationManager startMonitoringSignificantLocationChanges];
}

- (void)restartMonitoringLocation {
    [_anotherLocationManager stopMonitoringSignificantLocationChanges];

    if (IS_OS_8_OR_LATER) {
        [_anotherLocationManager requestAlwaysAuthorization];
    }
    [_anotherLocationManager startMonitoringSignificantLocationChanges];
}


#pragma mark - CLLocationManager Delegate

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{

    NSLog(@"locationManager didUpdateLocations: %@",locations);

    for (int i = 0; i < locations.count; i++) {

        CLLocation * newLocation = [locations objectAtIndex:i];
        CLLocationCoordinate2D theLocation = newLocation.coordinate;
        CLLocationAccuracy theAccuracy = newLocation.horizontalAccuracy;

        self.myLocation = theLocation;
        self.myLocationAccuracy = theAccuracy;
    }

    [self addLocationToPList:_afterResume];
}



#pragma mark - Plist helper methods

// Below are 3 functions that add location and Application status to PList
// The purpose is to collect location information locally

- (NSString *)appState {
    UIApplication* application = [UIApplication sharedApplication];

    NSString * appState;
    if([application applicationState]==UIApplicationStateActive)
        appState = @"UIApplicationStateActive";
    if([application applicationState]==UIApplicationStateBackground)
        appState = @"UIApplicationStateBackground";
    if([application applicationState]==UIApplicationStateInactive)
        appState = @"UIApplicationStateInactive";

    return appState;
}

- (void)addResumeLocationToPList {

    NSLog(@"addResumeLocationToPList");

    NSString * appState = [self appState];

    self.myLocationDictInPlist = [[NSMutableDictionary alloc]init];
    [_myLocationDictInPlist setObject:@"UIApplicationLaunchOptionsLocationKey" forKey:@"Resume"];
    [_myLocationDictInPlist setObject:appState forKey:@"AppState"];
    [_myLocationDictInPlist setObject:[NSDate date] forKey:@"Time"];

    [self saveLocationsToPlist];
}



- (void)addLocationToPList:(BOOL)fromResume {
    NSLog(@"addLocationToPList");

    NSString * appState = [self appState];

    self.myLocationDictInPlist = [[NSMutableDictionary alloc]init];
    [_myLocationDictInPlist setObject:[NSNumber numberWithDouble:self.myLocation.latitude]  forKey:@"Latitude"];
    [_myLocationDictInPlist setObject:[NSNumber numberWithDouble:self.myLocation.longitude] forKey:@"Longitude"];
    [_myLocationDictInPlist setObject:[NSNumber numberWithDouble:self.myLocationAccuracy] forKey:@"Accuracy"];

    [_myLocationDictInPlist setObject:appState forKey:@"AppState"];

    if (fromResume) {
        [_myLocationDictInPlist setObject:@"YES" forKey:@"AddFromResume"];
    } else {
        [_myLocationDictInPlist setObject:@"NO" forKey:@"AddFromResume"];
    }

    [_myLocationDictInPlist setObject:[NSDate date] forKey:@"Time"];

    [self saveLocationsToPlist];
}

- (void)addApplicationStatusToPList:(NSString*)applicationStatus {

    NSLog(@"addApplicationStatusToPList");

    NSString * appState = [self appState];

    self.myLocationDictInPlist = [[NSMutableDictionary alloc]init];
    [_myLocationDictInPlist setObject:applicationStatus forKey:@"applicationStatus"];
    [_myLocationDictInPlist setObject:appState forKey:@"AppState"];
    [_myLocationDictInPlist setObject:[NSDate date] forKey:@"Time"];

    [self saveLocationsToPlist];
}

- (void)saveLocationsToPlist {
    NSString *plistName = [NSString stringWithFormat:@"LocationArray.plist"];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docDir = [paths objectAtIndex:0];
    NSString *fullPath = [NSString stringWithFormat:@"%@/%@", docDir, plistName];

    NSMutableDictionary *savedProfile = [[NSMutableDictionary alloc] initWithContentsOfFile:fullPath];

    if (!savedProfile) {
        savedProfile = [[NSMutableDictionary alloc] init];
        self.myLocationArrayInPlist = [[NSMutableArray alloc]init];
    } else {
        self.myLocationArrayInPlist = [savedProfile objectForKey:@"LocationArray"];
    }

    if(_myLocationDictInPlist) {
        [_myLocationArrayInPlist addObject:_myLocationDictInPlist];
        [savedProfile setObject:_myLocationArrayInPlist forKey:@"LocationArray"];
    }

    if (![savedProfile writeToFile:fullPath atomically:FALSE]) {
        NSLog(@"Couldn't save LocationArray.plist" );
    }
}


@end

Do not forget of enable backgound location in plist file.

enter image description here

And set message to LocationAlwaysUsageDescription.

enter image description here

Upvotes: 8

Related Questions