CLLocationManager sometimes stops updating while app is in background - iphone

I have an app which uses CLLocationManager to track the user's route, drawing dots along the path taken. The app runs in the background using Required background modes > App registers for location updates.
As I understand, anything that happens in the background needs to be called from locationManager:didUpdateToLocation:fromLocation as this is the method that gets called with each location update.
The problem I'm having is that sometimes this stops getting called. It seems to happen when the user's location does not change much within the space of maybe 15 minutes or so. As far as I can tell, calls to locationManager:didUpdateToLocation:fromLocation just stop, presumably to save the battery. Unfortunately, it doesn't resume again when you're back on the move.
I presume there's no way to override this behaviour, so I would like to use Notification Centre to inform the user that the app is no longer recording the route. The problem is, how can the app know that this has happened? If locationManager:didUpdateToLocation:fromLocation is not called, I can't fire my notification. If it is being called, the notification should not fire.
Is there some kind of system notification that says location updates will cease?
I'm finding it quite hard to debug this as I can't take my Mac everywhere when I'm out and about testing the location on the device (there's only so much you can do in the simulator). Any tips for debugging would also be much appreciated!

If you haven't found the answer, I think it is because of a new attribute added to CLLocationManager called pausesLocationUpdatesAutomatically. The attribute defaults to YES, and its behaviour is exactly as you describe. Try setting it to NO and I think it will fix your problem.

Starting in iOS9, make sure you're also setting this property on your location manager:
[locationManager setAllowsBackgroundLocationUpdates:YES]

There's a delegate for location update did Fail
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
There are a few kinds of errors: kCLErrorDenied kCLErrorNetwork Add code here to handle them in the delegate method above not updating location, perhaps a UIAlertView to tell the user.
Personally, I call [locationManager stopUpdatingLocation]; on any error then restart it with an error message depending on the reason for the failure.
ALSO re background, check code in your appDelegate:
- (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.
[self saveContext];
if ([CLLocationManager significantLocationChangeMonitoringAvailable]) {
// Stop normal location updates and start significant location change updates for battery efficiency.
[self.locationHandler.locationManager stopUpdatingLocation];
[self.locationHandler.locationManager startMonitoringSignificantLocationChanges];
}
else {
NSLog(#"Significant location change monitoring is not available.");
}
}
LASTLY re: testing. You can simulate some errors in location by changing the location movement in the simulator. For example, going from running to driving will cause an error. Going from running to a single specific custom location will cause an error. They should all appear in the delegate method for locationManager above.

I've managed to solve the problem by adding a local notification that fires with a 90 second delay every time a new location is added to the route. When the next location is added, the previous notification is cancelled and a new one is scheduled. This way, if it stops updating, a notification is received by the user (albeit with a 90 second delay). It's not ideal, and it may not be great for battery life, but it is a solution and it's the best I've got for the time being.

#Ron, I meet the same problem as beev describe, and i had already set pausesLocationUpdatesAutomatically to NO. I think because iOS will kill some apps that didn't be triggered in 10 minutes when it's under background. So add local notification maybe a good choice at the moment.

Related

iOS - accelerometer in background shows location services popup, but we don't need location services

Our app would like to access accelerometer data in background. A possible way to achieve is to use Core Motion for accelerometer readings -
with
CLLocationManager* locationManager;
CMMotionManager* motionManager;
...
[motionManager startAccelerometerUpdatesToQueue: ... withHandler: ...]
which works fine on the foreground, but the only way I've found to receive the updates in background is to set the app to allow using Location in background, and call
[locationManager startUpdatingLocation]
in -applicationWillResignActive:
The problem is, when I call startUpdatingLocation that a window pops up with text Turn On Location Services to Allow "app" to Determine Your Location.
But of course, I receive accelerometer readings regardless of whether Location Services are enabled, but the popup is annoying and will probably confuse users.
Is getting accelerometer data in the background somewhat tied to attempting to receive location updates?
You can use startAccelerometerUpdatesToQueue in background, the only thing you need to meet — use any background mode to make you app running in the background (location updates (your case), playback, VoIP or BT4 central).

CLLocationManager stops in background

I have an app which uses CLLocationManager to track the user's route, drawing dots along the path taken. The app runs in the background using Required background modes > App registers for location updates.
As I understand, anything that happens in the background needs to be called from locationManager:didUpdateToLocation:fromLocation as this is the method that gets called with each location update.
The problem I'm having is that sometimes this stops getting called. It seems to happen when the user's location does not change much within the space of maybe 15 minutes or so. As far as I can tell, calls to locationManager:didUpdateToLocation:fromLocation just stop, presumably to save the battery. Unfortunately, it doesn't resume again when you're back on the move.
I presume there's no way to override this behaviour, so I would like to use Notification Centre to inform the user that the app is no longer recording the route. The problem is, how can the app know that this has happened? If locationManager:didUpdateToLocation:fromLocation is not called, I can't fire my notification. If it is being called, the notification should not fire.
I don't think that there is any way to be notified that the location manager has stopped sending you events, but there is a way to prevent it from happening. In iOS 6, a new feature was added that allows the location manager to power down services if it doesn't think they are being used. If you do the following, the location manager will continue sending you events in the background until you run out of battery:
if ([self.locationManager respondsToSelector:#selector(pausesLocationUpdatesAutomatically)])
{
self.locationManager.pausesLocationUpdatesAutomatically = NO;
}
Also, in iOS6, you should be using locationManager:didUpdateLocations: as locationManager:didUpdateToLocation:fromLocation: is deprecated.
The 2 delegate methods:
-(void)locationManagerDidPauseLocationUpdates:(CLLocationManager *)manager
and
-(void)locationManagerDidResumeLocationUpdates:(CLLocationManager *)manager
tells you when the location updates stop and start due to pausing.

How to get location when app goes to background or phone is locked?

I'm trying using locationManager to get my location and update it to my webservice.
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
//some code here
}
It's normal, but if my app goes to the background or phone screen is locked, updating location may be paused. How do I get the location anyway?
Read the full blog post on it: iPhone Background GPS: accurate to 500m, not enough for foot traffic
Here is a tutorial: iOS Multitasking: Background Location
I recommend you set up a handler such as this example:
TTLocationHandler
Configure the handler for the recency and accuracy required. Set up a listener for the notification "LocationHandlerDidUpdateLocation". Put your code to upload the data in a method called on receipt of that notification.
If you do not require fine grain detail, I would advise you use the battery saving options of significantLocationChange monitoring in background. In that case you will not need to add background mode in info.plist and you will not waste resources unnecessarily. The app will be awakened and your method called only on significant movement of the user. There is configurable options in the handler to require constant monitoring in background and/or foreground, and to set recency and accuracy thresholds.
See the LMPinTracker class for example of how to respond and save your data to web or locally. See the thread Invoke get current coordinates every few seconds without NSTimer for discussion of use.
Add This Key to your Application Plist
Required background modes
Mark it as Array, and on its item0 add this Value
App registers for location updates
The app will now get location data in Background too.

Core Location: toggle startMonitoringSignificantLocationChanges down from kCLDistanceFilterNone

In order to conserve battery, I monitor location updates to see if the user has been stationary for a period of time; if so, I then downgrade the CLLocationManager from its main settings of kCLLocationAccuracyBestForNavigation and kCLDistanceFilterNone (maximum settings) to monitoring significant location changes only.
The trouble is, it doesn't work: after calling startMonitoringSignificantLocationChanges, the location updates continue to pour in at a high rate as they did prior to the call.
How do you wind activity down and then back up again?
Update: this code answers the question:
//Set
if ( shouldMonitorSignificantChangeUpdates ) {
NSLog(#"Entering -> significant change mode");
[self.locationManager stopUpdatingLocation];
[self.locationManager startMonitoringSignificantLocationChanges]; //aka stop monitoring every location change
} else {
NSLog(#"Exiting <- significant change mode");
[self.locationManager stopMonitoringSignificantLocationChanges]; //aka begin monitoring every location change
[self.locationManager startUpdatingLocation];
}
From the docs:
startMonitoringSignificantLocationChanges does not rely on the value in the distanceFilter property to generate events
Instead of using startMonitoringSignificantLocationChanges, use a timer to stop and start the location updates.
You could also try turning off location updates, then turning it back on using startMonitoringSignificantLocationChanges. Don't forget that this will make the system launch your app after it's been terminated when it detects a significant location change. It doesn't look like that's what you really want.
Actually the code as edited in the question does work. It allows the system to go back and forth from "constant stream of locations" to "occasional updates" as desired.
I think my initial testing was simply not aggressive enough and the docs don't really discuss using both techniques, instead imagining an app which needs either navigation or significant changes only.

Save current state while fast context switching - iOS4

While going through the iOS-4 Multitasking for fast context switching, I have a doubt regarding save last state of application.
Do applications have to manually save the last state in "- (void)applicationDidEnterBackground:(UIApplication *)application"? Or iOS-4 will take care of it?
In the video it's mentioned as follows:
-(void)applicationDidEnterBackground:(UIApplication *)application {
// save app state
[self saveState];
// reduce memory usages
....
// prepare UI
....
// close listening sockets
....
}
Thanks in advance,
Sunil
Once your application has entered background, there's no guarantee it will ever come back to the foreground. It may at any point in time be terminated, without notification of any kind. Thus, entering background, you want to save the state or risk losing it.
To quote Apple (source: http://developer.apple.com/iphone/library/documentation/iphone/conceptual/iphoneosprogrammingguide/BackgroundExecution/BackgroundExecution.html),
Save your application state before moving to the background. During low-memory conditions, background applications are purged from memory to free up space. Suspended applications are purged first, and no notice is given to the application before it is purged. As a result, before moving to the background, an application should always save enough state information to reconstitute itself later if necessary. Restoring your application to its previous state also provides consistency for the user, who will see a snapshot of your application’s main window briefly when it is relaunched.
Do applications have to manually save the last state in "- (void)applicationDidEnterBackground:(UIApplication *)application"? Or iOS-4 will take care of it?
Yes, if you want your app to restore after it's been killed, you need to manually save state here.
you should use these 2 methods in your app delegate to save current state before entering background/terminate.
- (void)applicationWillResignActive:(UIApplication *)application {
/*
Sent when the application is about to move from active to inactive state.
This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message)
or when the user quits the application and it begins the transition to the background state.
Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates.
Games should use this method to pause the game.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application {
/*
Called when the application is about to terminate.
See also applicationDidEnterBackground:.
*/
}