Using startMonitoringSignificantLocationChanges with NSTimer to improve battery usage - ios

I'm currently working in an iOS app that other developer started. The app needs to monitor location changes because it needs to know the user position with low precision (hundred meters). The previous implementation of the location stuff was done using an NSTimer and startUpdatingLocation. The execution goes like this:
// Fire each 10 seconds start updating location
self.timerPosition = [NSTimer scheduledTimerWithTimeInterval:ti
target:self
selector:#selector(location)
userInfo:nil
repeats:YES];
[self.timerPosition fire];
Location selector does this
// Configure & check location services enabled
...
self.locman.delegate = self;
self.locman.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[self.locman startUpdatingLocation];
And then in the location manager delegate
[manager stopUpdatingLocation];
But reading about getting the user location in Apple docs, it seems that the right way to get the location with low-power consumption is to use startMonitoringSignificantLocationChanges.
My question is, is a good decision to keep the location timer in combination with startMonitoringSignificantLocationChanges instead of startUpdatingLocation, or it's a nonsense approach?
I do not need to get location when the app is in the background, but I want to know when the user has changed it's position when the app is active.

I can tell you that the timer can't and won't be needed when you are using the low-power -startMonitoringSignificantLocationChanges. That method only responds to callbacks from the delegate when the device detects a change. This check for location is not using GPS, it uses Wifi and cell-tower triangulation which is already happening. So by using this method, there is no need to slow it down to save battery life. Just set up the delegate methods and respond accordingly.
I'm not sure what your implementation of location is for, but the region monitoring is also another great way to get location updates using little to no battery. Region monitoring is much more helpful when you have specific locations to monitor rather than just general user location. Hope this clears things up.

Related

CoreLocationManager never pauses?

Trying to get my app to use less power, it's tracking location always in the background but I'd like for it automatically pause so I can turn on region watching and use that to resume precise location monitoring once the user moves around a bit.
I've had the app on for half an hour now and the location service is not pausing. I think this has been the case since Apple changed location stuff in iOS 13? I'm not really sure. All the documentation I can find online seems extremely outdated.
Any advice is greatly appreciated, relevant code follows:
init() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 10
locationManager.activityType = .fitness
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.showsBackgroundLocationIndicator = true
locationManager.startUpdatingLocation()
}
func locationManagerDidPauseLocationUpdates(_ manager: CLLocationManager) {
delegate?.paused(tracker: self)
print("MT | LOCATION SERVICES PAUSED!") <---- NEVER GETTING CALLED (been running for 40 minutes now, no location updates, still going though?)
// if not already, start region monitoring
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Collect data
for location in locations {
print("MT | New Location: \(location)")
}
}
I wouldn't rely on connecting to Xcode. Xcode is generally greedy. It doesn't want you to stop your debugging. So it never puts the app in a suspended state.
The callback of locationManagerDidPauseLocationUpdates is somewhat identical to your app being suspended which Xcode prevents.
The way I would test this is to use os.log. Then log the outputs to your Mac's console app. See here.
Just make sure the app is not launched from you running from Xcode. I'd rather disconnect your device from Xcode, kill the app. Disconnect your device from Xcode. Then just tap on the app icon again. That way Xcode cannot intervene.
The issue is caused by your desired accuracy.
You are setting the accuracy to the best, which will make your application run continuously, so it will never pause in any iOS versions.
Furthermore, in a practical test whether it is pausing or not, change the accuracy to kCLLocationAccuracyHundredMeters. It will definitely pause. You can change the filter accuracy as you need for the next update.
Do you think maybe your activityType is preventing the location services to pause?
activityType
The location manager uses the information in this property as a cue to determine when location updates may be automatically paused. Pausing updates gives the system the opportunity to save power in situations where the user's location is not likely to be changing.
.fitness
This activity might cause location updates to be paused only when the user does not move a significant distance over a period of time.
I don't know what your particular use case is but, have you considered using the following function for background location tracking?
startMonitoringSignificantLocationChanges()
You may also try a mix of both, turning on significant location changes for day to day use and allowsBackgroundLocation when doing something like tracking a run, etc.
I think it is quite clear from the docs that this the pause functionality does not work like that. First of all, there is no guarantee that the GPS hardware will pause at all, it depends on the type of activity as well as other activities in the system. It is up to iOS to decide when and if to pause location updates, the property is just a hint.
More important, your use case would not work since if it gets paused, the user would have to interact with your app manually to resume updates.
Apple mentions your case specifically and recommends reducing the accuracy of location updates so it is only using cell tower triangulation instead of GPS (see docs above).
So, instead of using pausesLocationUpdatesAutomatically = true, you could do desiredAccuracy = kCLLocationAccuracyThreeKilometers.
To quote Apple,
[...] consider disabling this property and changing location accuracy to kCLLocationAccuracyThreeKilometers when your app moves to the background. Doing so allows you to continue receiving location updates in a power-friendly manner
Doesn't this cover your use case?

iOS GPS tracking app that runs all the time

I am trying to make an app to track the user GPS all the time, this app is a kind of car GPS tracker to get the location of driver all the time and send it to server.
I have tried to add "location updates" to the "background modes" but the app will automatically suspends after 10 mins when going into background.
Is there a way to make this app run all the time and get the GPS location?
You have two options here:
1) Regular location tracking. This type of tracking works with kCLAuthorizationStatusAuthorizedWhenInUse and kCLAuthorizationStatusAuthorizedAlways authorizations. When CLLocationManager started tracking location once it will receive location updates in delegate method locationManager:didUpdateLocations:. App can go to suspended state, but when location manager receive new location app goes to background state and handles new location in delegate method. How to setup location manager:
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
// Setup location tracker accuracy
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
// Distance filter
self.locationManager.distanceFilter = 50.f;
// Assign location tracker delegate
self.locationManager.delegate = self;
// This setup pauses location manager if location wasn't changed
[self.locationManager setPausesLocationUpdatesAutomatically:YES];
// For iOS9 we have to call this method if we want to receive location updates in background mode
if([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]){
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
[self.locationManager startUpdatingLocation];
}
2) Signification location changes tracking. This type of tracking works only with kCLAuthorizationStatusAuthorizedAlways authorization. It receives new location only each 500 meters, so distance filter and desiredAccuracy don't work here. App can go to suspended state, and even can be terminated by system, but when location updates app goes to background state and receives location in delegate method locationManager:didUpdateLocations:.If app was terminated by system, it will be relaunched in background with UIApplicationLaunchOptionsLocationKey key in launch options in didFinishLaunchingWithOptions app delegate method. How to setup this type on tracking:
- (void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
// Assign location tracker delegate
self.locationManager.delegate = self;
// For iOS9 we have to call this method if we want to receive location updates in background mode
if([self.locationManager respondsToSelector:#selector(allowsBackgroundLocationUpdates)]){
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
}
[self.locationManager startMonitoringSignificantLocationChanges];
}
You should notice that both of these methods does not guarantee that your application does not go to suspended state.
Also, if app was terminated by user (for example from app switcher by swipe) location tracking in background will not work.
UPDATE (corresponding to comments)
Here is my code examples that work for me:
For Regular tracking. Run the example, provide access to user location, tap Start button to start location updates. To test locations in simulator choose in simulator menu Debug > Location > Freeway Drive. Now you can push app to background by home button (Command+Shift+H). Leave simulator for more than 10 minutes, and all this time app will receive locations. When you return to app you will see red pins on the map.
For Significant changes. Run the app and test by the same way as for previous example.
Monitoring Significant changes can be started only by method [self.locationManager startMonitoringSignificantLocationChanges];
UPDATE (iOS 11)
Changes to location tracking in iOS 11
iOS 11 also makes some major changes to existing APIs. One of the affected areas is location tracking. If your app only uses location while the app is in the foreground, as most apps do, you might not have to change anything at all; however, if it’s one of those apps that continuously track user’s location throughout the day, you should probably book some time this summer for making some changes in how you do the tracking and testing possible usage scenarios.
follow this link: https://mackuba.eu/2017/07/13/changes-to-location-tracking-in-ios-11/
I am sure it is useful for the author because the question was asked in Feb 2016 and I am giving an answer in June 2019. This answer maybe is useful for other users.
Recently, I was working with the same requirement. After 2-3 week hard work, I did it. For other users, I create a helper class for it. Which is available in GitHub.
Please use HSLocationManager for your requirement. I have achieved the same requirements in one of my project
Location manager that allows getting background location updates every
n seconds with desired location accuracy.
Advantage:
OS will never kill our app if the location manager is currently
running.
Give periodically location update when it required(range is between 2 -
170 seconds (limited by max allowed background task time))
Customizable location accuracy and time period.
Low memory consumption(Singleton class)
In reply to comment 1 in the solution (I can't comment anywhere yet): you didn't seem to solve the problem as your app gets suspended and doesn't update the location any more after 10 minutes.
I had the same issue: I had set setAllowsBackgroundLocationUpdates to YES, and I had the NSLocationAlwaysUsageDescription key in my Info.plist, but my App also used to stop tracking location after 10 minutes.
I solved it by adding both NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription to the Info.plist file so it looks like this:
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs to use your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs to use your location</string>
set it.it take power of battery but your application run in Background.OS does not Suspend your App
[self.locationManager setPausesLocationUpdatesAutomatically:NO];

How to stop using the Location Services within my iOS app?

I have an iOS app that uses the CLLocationManager to monitor regions and to get GPS updates. Most of the time, I want my app to continue tracking the cellphone when it goes in background or even when it gets killed, and it works well (I can still see the small arrow in the status bar after my app gets killed). The problem is that I am not able to stop monitoring the regions and GPS updates after my app has been restarted by the Location Services.
When my app gets restarted by the Location Services, I instanciate the CLLocationManager and then call its methods stopRangingBeaconsInRegion and stopUpdatingLocation before setting its delegate to nil and itself to nil.
Thanks to NSLogger, I can see that my callbacks are no longer called, but the small arrow in the status bar stays there (and my app is the only one that I allowed to use the Location Services from the settings menu).
What did I miss? Is there a way to know what still uses the Location Services in my app?
When you call stopRangingBeaconsInRegion, where are you getting the list of regions? The proper way to do this is like below:
for (CLRegion *monitored in [self.locationManager monitoredRegions]) {
NSLog(#"Stopping monitoring on: %#", monitored.identifier);
[self.monitoringLocationManager stopMonitoringForRegion:monitored];
}
for (CLBeaconRegion *region in [self.locationManager rangedRegions]) {
NSLog(#"Stopping ranging on: %# ", region.identifier);
[self.rangingLocationManager stopRangingBeaconsInRegion:region];
}
I finally found that I missed to remove some of my numerous regions. The easy way to avoid this mistake is to retrieve the list of regions monitored with the property monitoredRegions and call stopRangingBeaconsInRegion for each of them. I also forgot to call stopMonitoringSignificantLocationChanges (I didn't know that my app was using it, since I am modifying the app of a former colleague).
How do you know that it is your app that is using the location services?
The small arrow appears if the iOS itself is using the location services in the background.
if you did call stopRangingBeaconsInRegion and stopUpdatingLocation before setting the delegate to nil and you are not getting any callbacks then your app is not using the location services.

CLLocationManager in ios7 stops working

My CLLocationManager starts when the user first enters the app. I am initializing it like this:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = kDistanceFilter;
self.locationManager.headingFilter = kHeadingFilter;
And I am using geofencing.
I have defined in my .plist the required background modes.
If I test the app in the simulator using a gpx file, it works fine. So far, so good.
However, I read in these two links:
Start Location Manager in iOS 7 from background task
Background Location Services not working in iOS 7
That in iOS7, when the location does not change for a couple of minutes then the CLLocation goes to sleep.
My question is that:
I do not call didUpdateLocation at all, I only want the didEnterRegion, or didExitRegion. Theoretically, will it work , even the app is killed or user Location has not changed in the last hour and then decides to move?
There are a few elements in your questions:
1- In order not to rehash what is in a previous answer, I would first look at my answer at this link. It will help you resolve around the core location updates stopping if the user stops moving and how you can modify that behaviour:
iOS7 Core Location not updating
2- If the user kills an app in iOS7 (by flicking it up in the app switcher), the app will not restart and as such neither your location updates nor region monitoring will restart after the phone is restarted. On the other hand, if the app is killed because of memory pressures, iOS will restart the app and relaunch your location updates and region monitoring in the background. Also, if you reboot the phone, region monitoring will restart automatically. You obviously must have the background mode enabled.
3- Now you are talking about regions in your questions so I am assuming you have implemented CLCircularRegion. If not, that should be the first step and then "startMonitoringForRegion". In your case, even if the user has stopped moving for a while and then started moving, the app will be awaken/delegate called when the app enters or exit a region.
4- Make sure to use locationManager: didDetermineState: forRegion: as well. This will allow the app to determine if it is inside or outside of the region at start. This is useful when you think of an app being launched and no region crossing has happened but it is important to know whether the user is inside or outside of the region.
Hope this helps

When to call [clLocationManager stopUpdatingLocation]

To save power in my app I have decided to use a mix of startUpdatingLocation when the app is active and go into startMonitoringSignificantLocationChanges mode when the app is in the background. Basically I do the following when the app goes into the background:
-(void)applicationDidEnterBackground:(UIApplication *)application{
[myLocationManager startMonitoringSignificantLocationChanges];
}
And when the app comes back into the foreground I do the following:
-(void)applicationDidBecomeActive:(UIApplication *)application{
//Other irrelevant code
[myLocationManager stopMonitoringSignificantLocationChanges];
[myLocationManager startUpdatingLocation];
}
This seems logical, to me anyways. My question is, should I be calling the stopUpdatingLocation method in the applicationDidEnterBackground event? Like so:
-(void)applicationDidEnterBackground:(UIApplication *)application{
[myLocationManager stopUpdatingLocation];
[myLocationManager startMonitoringSignificantLocationChanges];
}
Where exactly should I be calling the stopUpdatingLocation method?? Please tell me if there is more than one place where this should be done. I'm assuming any error event should stop the updating?
I don't see anything wrong with what you are doing. Note that I have a commercial app that heavily uses location services, and I'm in the midst of rewriting it to improve it's performance and minimize battery usage.
My released version uses sigLocationChanges predominantly (in background & foreground), but switches to using startUpdatingLocation when unhappy with the quality of the location sigLocationChanges gives me, since my UI has to display the users location roughly accurately. I call stopUpdatingLocation immediately after each event to minimize battery drain. In my shipping version this seems to work okay, but my log files have found a tiny subset of users who seem to constantly get poor locations and I'm spinning up the GPS hardware more than I like.
Also in Privacy Settings, the type of location icon displayed for your app will be determined by when you last used the full GPS location mode. Mine always shows the location icon that indicates a heavy battery impact, even if I'm only using startUpdatingLocation briefly a couple times per day, which can make my users paranoid about how my app affects their battery life.
In my new release, to minimize the battery drain of using of startUpdatingLocation, I've cut it's use back to hopefully nil. When the app activates, I now get the current location directly from the location manager, cLLocMgr.location. Typically that's an accurate location, and my UI can be instantly drawn (or refreshed) correctly. I also check it again when certain views are activated to ensure if the user is moving while keeping my app open the display keeps up. Now I only spin up the GPS hardware if the phone has a bad location in a specific situation where a good location is absolutely required in the app. In that case, I limit it's use to 2 minutes (I'm assuming 2 minutes is long enough to get best location from GPS hardware), and wait at least 10 minutes before allowing it's use again.
Your question doesn't give me enough info to tell how accurate you need to be and how dynamic your location display is. But unless you need super accuracy and dynamic display, you should consider just using the current location without spinning up the GPS hardware to save battery.
Edit: Here is the actual code I used for Jeraldo, cleaned up a bit. Note it's been a year since I touched it, so I'm a little rusty on it, hope I didn't clean up anything.
// Called at start to ask user to authorize location data access.
- (void) requestIOSLocationMonitoring {
#if TARGET_IPHONE_SIMULATOR
// If running in siumaltor turn on continuous updating (GPS mode)
// This is for testing as significant change isn't useful in simulator
// Set a movement threshold for new events. This is only used by continiuous mode, not sig change events
// Keep it as low as possible,but not so low as to generate spurious movements.
cLLocMgr.distanceFilter = 30;
// Use continuous location events in simulator.
// desired accuracy only works in continuious (high power) mode.
cLLocMgr.desiredAccuracy = kCLLocationAccuracyBest;
[cLLocMgr startUpdatingLocation];
#else
// If not in simulator app's default is significant change monitoring
[cLLocMgr startMonitoringSignificantLocationChanges];
#endif //TARGET_IPHONE_SIMULATOR
}
// Toggle back and forth between continius updates (GPS on) and
// significant change monitoring
- (void) setGPSMode: (bool) useGPSMode {
// Keep track of time since we last changed GPS mode
NSTimeInterval secsInThisMode = [[NSDate date] timeIntervalSinceDate: lastModeChange];
// inGPSMode is an object instance variable app uses to track mode it is in.
if (inGPSMode != useGPSMode) {
lastModeChange = [NSDate date];
if (!useGPSMode) {
// Tell app to operate as if continuous updating is off
inGPSMode = false;
#if TARGET_IPHONE_SIMULATOR
// But keep using continuous location events in simulator for testing.
cLLocMgr.distanceFilter = 30;
#else
// Turn off continious updates for app on actual devices
[cLLocMgr stopUpdatingLocation];
#endif
} else if (secsInThisMode > cMinGPSModeBreak) {
// Only turn on continuous updating again if it's been off long enough
// Prevents GPS mode from running continiously and killing battery life
inGPSMode = true;
cLLocMgr.desiredAccuracy = kCLLocationAccuracyBest;
cLLocMgr.distanceFilter = kCLDistanceFilterNone;
[cLLocMgr startUpdatingLocation];
}
}
}

Resources