Reputation: 115
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
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
Reputation: 1039
I found a solutions for this. This worked for me in IOS 9. Even restarting the IOS device, keeps running.
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.
And set message to LocationAlwaysUsageDescription.
Upvotes: 8