App consuming too much battery - ios

After building the iOS app I wanted, I realized it's consuming way too much battery due to location services.
Background: My app makes a call to the server over network every time a user moves 10 meters (30 ft). This triggers the didUpdateLocation function. When that function is triggered I use NSURLSession to send the users coordinates to the server, that's really about it.
Now the issue lies in that I can't power down location services hardware by calling locationManager.stopUpdatingLocation() after the first update because I won't get any subsequent updates when the user decides to move again. In addition, location services stays active while the app is in the background to monitor movement, also causing a drag on battery life. I have read almost everything about this issue when it comes to documentation and other questions on here.
A lot of other questions suggest using an NSTimer and calling the startUpdatingLocation() after an interval of time has passed by and I have received the first location update and called stopUpdatingLocation in my didUpdateLocation function. So I tried the following:
let timerAction = NSTimer(timeInterval: 6.0, target: self, selector: #selector(MapViewController().startLocationServices), userInfo: nil, repeats: true)
timerAction.fire()
func startLocationServices() {
self.locationManager.startUpdatingLocation()
}
This hasn't worked for some reason or another. But my question really focuses around how I can save battery and make my app really energy efficient when I must monitor a user's movements in the foreground and background with the distanceFilter = 10.0?
Any suggestion maybe around what you have done or what you would recommend would be tremendously sought and appreciated.
Thank you!
Edit
#wain suggested using allowDeferredLocationUpdatesUntilTraveled:timeout:,
Start the delivery of location updates before calling this method. The most common place to call this method is in your delegate’s locationManager:didUpdateLocations: method. After processing any new locations, call this method if you want to defer future updates until the distance or time criteria are met. If new events arrive and your app is in the background, the events are cached and their delivery is deferred appropriately.

You have 2 options:
Use allowDeferredLocationUpdatesUntilTraveled:timeout:
Use significant location change
Try the first, if it isn't available on the device you must sacrifice your 10m goal

I ended up remedying this problem by using geofencing. I create a CLCircularRegion and call startMonitoringForRegion(_:). Every time the user leaves the region (10 meters), I make a request to the server to see where he's at now and update the region. This use little to no battery and is exactly what I'm looking for. Look at the CLLocationManager, CLLocationManagerDelegate and CLCircularRegion classes on how to do this. I hope this will come in handy for someone.

Related

Alternatives to CoreLocation's requestLocation method

I have an IOS app where I require the user's current location in order to make an HTTP request to get data about nearby locations. Since I just need the user's location once, I am using the CoreLocation requestLocation method. This issue with this is that it takes roughly 10 seconds (which is stated in the documentation). I realize that there will always be a delay which I am prepared for, but the delay on requestLocation is just too long and ruins the user experience.
I was wondering if anyone has thought of a way to get the users location (just once) in a more timely way.
One solution I thought of was perhaps calling the method within the AppDelegate, as the app starts, and then passing the data to the ViewControllers via some shared object. The issue I foresee here is that AppDelegate does complete it's application:didFinishLaunchingWithOptions: function very quickly so I don't think it'll improve my problem
Another solution I thought of could be to use the startUpdatingLocation method, and then use stopUpdatingLocation after receiving the first location. My issue with this is that startUpdatingLocation may also take a few seconds to start up (documentation), so again this may not improve my problem.
The location I get for the user does not need to be super accurate so I am willing to compromise on precision for performance.

Switching between significant-location-change monitoring and location-updates monitoring based on application state

We use significant-location-change monitoring to know about our user's whereabouts when the app is not running. This data is based on cellular towers signals and therefore is not very accurate. We would like to use more accurate data whenever it is possible, that is, when the app is active or running in the background.
The question is, should I use "startMonitoringSignificantLocationChanges" and "startUpdatingLocation" at the same time, or, should I switch between those two methods? And if the second option is better, what app delegate events should I use in order to perform the necessary switch?
Unless you want to support a feature that needs continuous high precision location stream, its best to avoid using the high accuracy GPS data via startUpdatingLocation all the time.
I understand from your question that you intend to use more accurate location only at certain points of interest, when your app is woken up. In that case, the second approach of switching-on the more accurate location data only when needed, would be a better idea.
locationManager(_:didUpdateLocations:) in your CLLocationManager delegate would be a good point to start this in your case. Remember that significant location change monitoring API wakes up the system at least once every 15 minutes, even if there are no location changes, until it is explicitly stopped. So one must use it with care.
Instrumenting accurate location tracking while making sure that you don't eat up all of the phone battery is a non-trivial problem to solve. You may want to give third party SDKs like Hypertrack, which specialise in this, a try.

Is there a CoreLocation notification or delegate method when accuracy improves?

If you look at a map, the radius of accuracy begins big and then gets smaller as it gets more accurate. But locationManager:didupdateLocations: doesn't get called again when that changes.
Is there something that is called on that event?
I believe that the didUpdateLocations: callback will actually be called again when accuracy is updated. According to this Apple docs on Core Location:
In addition to a location object’s timestamp, you can also use the
accuracy reported by that object to determine whether you want to
accept an event. As it receives more accurate data, the location
service may return additional events, with the accuracy values
reflecting the improvements accordingly. Throwing away less accurate
events means your app wastes less time on events that can’t be used
effectively anyway.
What sort of tests have you tried to improve accuracy? Did you try it on an iPhone? Did you set the delegate and the desiredAccuracy property properly?
If you provide the following code, you will get very frequent location updates.
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
But according to Apple, it is recommended to plug in the charger to the iOS device while location manager is active. This is because the battery power gets drained fast while getting this much location updates.
There are other levels of accuracy for CLLocationManager( kCLLocationAccuracyBestForNavigation, kCLLocationAccuracyBest, kCLLocationAccuracyKilometer, kCLLocationAccuracyHundredMeters etc..)
Is there something that is called on that event?
No. You continue to get the same callbacks, but the accuracy of each location is (potentially) improved. There is no callback for "accuracy improved".
The CLLocations you receive in the callback contains vertical and horizontal accuracy information.

Objective C: get location without delegate

I have a problem in Objective C (writing an app for the iPhone). I know how to use the locationManager and its delegates. Once I called startUpdatingLocation on the locationManager, it will call the delegate method didUpdateToLocation whenever the location is updated. But the problem is, that this way is now suitable for what I want to do. I have a method (in a class) which looks as follows:
#implementation SomeClass
- (someType)getDbContentsOrderByDistance
{
... //here I need the current location
return dbContents;
}
#end
Now this function selects entries from a sqlite database ordered by their distance to the users current location. That is why I need the location in this function. What I want to do in the above method is: get the location, then select the stuff from the database and return it. I know that I could just try to access locationManager.location.coordinate.longitude, but I know that there won't be anything useful inside until the first location update has arrived. And I don't want to start the location updates before (e.g. when starting the app) because that would not be very efficient if I only need the location once.
I know how to do it the way that the delegate is called as soon as a location update arrives. The problem is that I don't know how to get that updated location from there into my method above. Basically I would need to let the method 'wait' until there is the first location update and then continue execution with that location.
Thank you for your help!
The short answer is, you can't. Getting the users location is an asynchronous process, and MUST be an asynchronous process. The device has to fire up various hardware like the GPS, cell tower locator, and WiFi triangulation system, get input from those different devices, and synthesize that into a location. That takes multiple seconds to do.
Putz's suggestion of starting a timer is a good one. I would add a few things however.
Typically the first readings you get from the location manager are really bad and should be discarded. The first reading you get is usually the last location reading when the GPS was active, and will have an out-of-date timestamp. The accuracy reading in that location might appear quite good, but it's a lie. I have seen the first location reading be off by several kilometers. You need to check the timestamp on your location updates and discard any reading that is more than 1 second old.
Once you've discarded the stale readings, the first several location updates are often really bad because the GPS hasn't settled down yet. The horizontal accuracy reading (which is really a "circle of confusion", or radius of possible positions) is an absurdly large value, sometimes a kilometer or more. Again, you need to write your location manager delegate method to discard readings that are too inaccurate. I suggest discarding values with a horizontal accuracy reading of >= 100 meters. How much inaccuracy you can tolerate depends on the specific appellation, but beware of making the accuracy requirement too accurate. Sometimes the GPS refuses to settle down. If you require a 5 meter accuracy, you might not get an acceptable accuracy during the entire run of your app. (If you're in an urban environment, a building, or other area where there is a lot of interference/obstruction of GPS signals)
Once you finally do get a value that's accurate enough, save the location and set your locationAvailable flag to YES.
Note that instead of a timer you could use the notification manager to broadcast a "got a good location" message. Any object that needs location information could register for your notification, and get called when the location manager gets an acceptable location reading.
Start a timer that fires every second or so that calls the function you need the location in. check to see if the location has been set. If the location is valid then kill the timer, if not then let the timer continue and check again in one second or so.
The way of knowing if the location is valid is in the delegate method. Set a global variable like locationAvailable = false. Then when the delegate method gets called set that variable to true.
Using location property can solve the purpose
From apple documentation
location The most recently retrieved user location. (read-only)
#property(readonly, nonatomic, copy) CLLocation *location
The value of this property is nil if no location data has ever been retrieved.
In iOS 4.0 and later, this property may contain a more recent location object at launch time. Specifically, if significant location updates are running and your app is terminated, this property is updated with the most recent location data when your app is relaunched (and you create a new location manager object). This location data may be more recent than the last location event processed by your app.
It is always a good idea to check the timestamp of the location stored in this property. If the receiver is currently gathering location data, but the minimum distance filter is large, the returned location might be relatively old. If it is, you can stop the receiver and start it again to force an update.
Whenever I create a new instance of location manager I get the most recent value of location in location property and since my distance filter is not set , it has the current timestamp. This means I dont have to call startUpdatingLocation and my data is accurate. I also need this for calculating distance between user location and a place so I can immediately return true or false if it is within the range. I find this very useful as I get location within 0.006 seconds and handy and got it from apple documentation only but still I dont know if it is a best practice.

How do startMonitoringSignificantLocationChanges and startUpdatingLocation effect one-another?

What happens if I call startUpdatingLocation while startMonitoringSignificantLocationChanges is running? Does significantLocationChange monitoring get stopped? If I then stopUpdatingLocation will significantLocationChange monitoring continue or restart?
I can find no documentation covering the interplay between these two ways of monitoring location.
I don't think the accepted answer really answers the question asked. I did some tests and you can use both if you want and they will not cancel each other out.
Why would someone want to use both ? Because startMonitoringSignificantLocationChanges wakes up the app from being suspended or terminated without the need of any background modes. So if you run both you can get accurate foreground location updates and significant location change background location updates.
Of course, you can switch the method, when going into background but a) that wasn't the question b) it adds unneeded logic
Of course, there's a question if running both methods drains more battery, but my bet is that it doesn't.
They are not meant to be used concurrently. It's either or as they both deliver heading and location changes to the same delegate method.
locationManager:didUpdateToLocation:fromLocation
They differ in the frequency and accuracy (and by extension hardware used and power consumption ) of the changes. You as the developer need to decide which is best based on your use case
I have a need to use both approaches at different times. When I switch from one to the other I set a flag that I can reference in my delegate so that I know the type of update.
Standard location service and significant location change service can be used together. Quote from the API Reference:
If both location services are enabled simultaneously, they deliver
events using the same set of delegate methods.
A good reason to use both is that the standard service gives better accuracy, while the significant location change service works even when your app is suspended.

Resources