ios swift
ios swift

Reputation: 11

Location access while app is terminated, without internet iOS

I am developing an iOS app, my questions are as below:

  1. Need to access user location every time while application is running or not?
  2. Is Location not depend on the internet?
  3. Apple will be approve this kind of app?

For question 1.

I am using these line for getting the user location

locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()

With help of these i am able to get location when app is in foreground or background.

When application terminate

locationManager.startMonitoringSignificantLocationChanges()

it work only when user cross the 500 meters

then application become active and update the location

2.These are depend on the internet, while i want that receive location everytime internet exist or not.

3.Please also let me know these kind of app will be approve or not from apple.

Help/Suggestion please.

Thanks in advance.

Upvotes: 1

Views: 3394

Answers (3)

AG.29
AG.29

Reputation: 250

Yes we can do it with more or less than 500m I have tested it, First of all I searched a lot in google and somewhere I found that it is working but I didn't believe it because as per apple security guidelines it seems not possible so I applied it in my code and test it. And the result is awesome. It saves all location data in plist locally when the app is even force quit by user,so you can see it through that plist. I didn't remember where I saw that link but I share my bunch of direct code which can help you to understand it.

And most interesting part I am capable to call a server when the app is in terminated state which is really superb. It seems Location Manager awake the app for some milliseconds when location changes.

LocationManager.h

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>

#define IS_OS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)

@interface LocationManager : NSObject

@property (nonatomic) CLLocationManager * anotherLocationManager;

@property (nonatomic) CLLocationCoordinate2D myLastLocation;
@property (nonatomic) CLLocationAccuracy myLastLocationAccuracy;

@property (nonatomic) CLLocationCoordinate2D myLocation;
@property (nonatomic) CLLocationAccuracy myLocationAccuracy;

@property (nonatomic) NSMutableDictionary *myLocationDictInPlist;
@property (nonatomic) NSMutableArray *myLocationArrayInPlist;

@property (nonatomic) BOOL afterResume;

+ (id)sharedManager;

- (void)startMonitoringLocation;
- (void)restartMonitoringLocation;

- (void)addResumeLocationToPList;
- (void)addLocationToPList:(BOOL)fromResume;
- (void)addApplicationStatusToPList:(NSString*)applicationStatus;

@end

LocationManager.m

#import "LocationManager.h"
#import <UIKit/UIKit.h>
#import "UpdateUserLocation.h"
#import "Constants.h"

@interface LocationManager () <CLLocationManagerDelegate>
@property (nonatomic , strong) UpdateUserLocation *updateUserLocation;
@end


@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.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"];
        [self updatePreferredUserLocation:[NSString stringWithFormat:@"%f",self.myLocation.latitude] withLongitude:[NSString stringWithFormat:@"%f",self.myLocation.longitude]];
    } 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" );
    }
}

Appdelegate.m

#import "LocationManager.h"

@property (strong,nonatomic) LocationManager * locationShareModel;

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

     enable when use track location at force quit
    [self.locationShareModel restartMonitoringLocation];
    [self.locationShareModel addApplicationStatusToPList:@"applicationDidEnterBackground"];
   }

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

    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

     enable when use track location at force quit
    [self.locationShareModel addApplicationStatusToPList:@"applicationDidBecomeActive"];

    //Remove the "afterResume" Flag after the app is active again.
    self.locationShareModel.afterResume = NO;
    [self.locationShareModel startMonitoringLocation];

}
- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
      enable when use track location at force quit
    [self.locationShareModel addApplicationStatusToPList:@"applicationWillTerminate"];

}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    NSLog(@"AG_Didfinishlaunching");

     //enable when use track location at force quit
    [self trackLocation:launchOptions];

}

- (void)trackLocation:(NSDictionary *)launchOptions {
    self.locationShareModel = [LocationManager sharedManager];
    self.locationShareModel.afterResume = NO;

    [self.locationShareModel 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) {
 ///////// delete this alert view if no need
        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];

        UIAlertController *showMsgAlertController = [UIAlertController alertControllerWithTitle: @"Eventseeker" message: @"To use all location servies. Please turn on Background App Refresh, go to Settings > General > Background App Refresh" preferredStyle: UIAlertControllerStyleAlert];
        UIAlertAction *showMsgAlertControllerOkAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
                                                                               handler:nil];
        [showMsgAlertController addAction:showMsgAlertControllerOkAction];
        dispatch_async(dispatch_get_main_queue(), ^{

            [self presentViewController:showMsgAlertController animated:YES completion:nil];
        });
 /////////
    } else if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted) {
        ///////// delete this alert view if no need
        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.locationShareModel.afterResume = YES;

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

Last but not the least don't forget to add the permission in plist for always access location. this is really important step to access it when app is force quit. User have to choose always use location access and must turn off battery saver mode as it disables background refresh mode. Keep remember Location always access and background refresh mode for this app must be On to access the location in terminated state.

For offline mode I didn't test it. But I think It works.

Upvotes: 0

Mr.Javed Multani
Mr.Javed Multani

Reputation: 13294

Your answers is here:

Question1: Need to access user location every time while application is running or not?

Ans: No, If you are using app and app is in running mode. You are continues taking update of location then you should always check the location accessibility and activenes like UBER app.

Question2: Is Location not depend on the internet?

Ans: Depends on your app's functionality. You are able to get current region by internet also or you can get it by using GPS.

Question3:Apple will be approve this kind of app?

Ans: Yes, If you are following proper guide lines then apple will definitely accept it. But make sure there are showing user friendly message while taking permission from user for access of location.

Note: Continues location update consume more power so it will speedly decrease the battery.

"it work only when user cross the 500 meters" For this you can set the accuracy for updation of location like...

locationManager.desiredAccuracy = kCLLocationAccuracyBest

Reference: Get User's Current Location / Coordinates

https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/LocationBestPractices.html

Upvotes: 2

Smartcat
Smartcat

Reputation: 2882

  1. Yes, you can write your app such that it can receive background location fixes, as your own code shows. You'll need to ask your users for background location permission, however, which many users are usually loathe to do for both privacy and battery reasons.

  2. Whether CLLocationManager uses internet (or even just Wifi access point detection) vs. GPS is very device dependent. See this SO answer for details.

  3. There are many apps in the App Store that do background location fixes via CLLocationManager. I'm routinely shocked at how many apps ask for Always location permission.

Upvotes: 0

Related Questions