iOS 12 being more aggressive about closing my app? - ios

So I wrote a stat tracker for skiing / snowboarding that was working just fine last season. This season I'm having the hardest time keeping the app alive even though I'm setup to receive background location updates.
Here are some of the tasks that are running in the background on timers:
-Parsing arrays / dictionaries and performing calculations
-Receiving and sending data to firebase
-Recording data to CoreData
As long I have cell signal things have a better chance of staying alive but no guarantee.
Did something change in iOS 12 vs 11?
Here's my code for starting location updates:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .other
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.showsBackgroundLocationIndicator = true
locationManager.startUpdatingLocation()
I've tried locationManagerDidPauseLocationUpdates to restart location updates if iOS tried to kill it, changing the activity type, changing the accuracy, nothing seems to work although changing the activity type to other did extend the amount of time before the app dies so I thought locationManagerDidPauseLocationUpdates would restart it but no dice.
Any other ideas on keeping things alive while recording?
edit: I've also tried checking for memory leaks
edit2: added screenshot of info.plist

So I don't know why but after some experimentation and moving things around I seem to have fixed it.
I had CllocationManager as a singleton and moved it to a struct as I'm sharing it between multiple VCs as to not instantiate it more than once.
I then moved
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .other
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.showsBackgroundLocationIndicator = true
out of its own function and into viewDidLoad() which still didn't work so I moved it to viewDidAppear() and now my app has been running for 8 hours without a hiccup. I'm not sure if the way I was sharing the location manager before was causing a second instantiation and a reset of the location manager settings to default which would cause locationManager.pausesLocationUpdatesAutomatically to become true (not sure just guessing)

Related

Starting locations updates after enter beacon region monitored Swift

I'm making a ibeacon region monitoring app with location updates when the user enter into this region (app not in foreground). This location updates must be configured as kCLLocationAccuracyBestForNavigation accuracy (because I have to make a tracking while the use remain in the region,
subscribe to me significant changes is not enough). Everything works well, but after 20 seconds (sometimes 1 minute o more) I stop receiving locations updates. I put all the keys in info.plist for always location usage, I include the background modes in capabilities section and locations updates on background.
I configure the locationManager with different configurations and always the SO stops my locations updates. I'm using IOS 12 and Iphone 7 for testing.
The way I configure CLLocationManager:
self.locationManager.desiredAccuracy
=kCLLocationAccuracyBestForNavigation
self.locationManager.activityType = .automotiveNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.delegate = self
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.pausesLocationUpdatesAutomatically = false
Start location updates (when user enter in Ibeacon Region):
func beaconManager(_ manager: KTKBeaconManager, didEnter region:
KTKBeaconRegion) {
self.locationManager.startUpdatingLocation()
}
And finally, in didUpdate locations i call a web service:
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
//Call web service using alamofire
}
I ask for your help to know if I am performing the settings correctly for the purpose I want to perform and any clue that lets me know why the operating system kills my process to get locations updates
Getting regular location updates in the background on iOS is tricky. The operating system is designed to keep apps from constantly running in the background to optimize battery usage, and it suspends them after a period of time unless you have several things exactly right.
You need to do three things:
You must get obtain always location permission from the user (as you say you've done).
You must add the following entry to your Info.plist. This will allow your app to run indefinitely in the background, however if you wish to submit your app to the App Store, this entry will also declare to reviewers that it is a background location app, and you will need to convince them that it provides a location-based benefit to the user, and that the user is aware of this benefit.
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
You must maintain a background thread to keep your app alive. It doesn't matter if you actually do anything in this background thread. Just having it be active keeps iOS from suspending your app.
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
func extendBackgroundRunningTime() {
if (backgroundTask != UIBackgroundTaskIdentifier.invalid) {
// already started
return
}
NSLog("Attempting to extend background running time")
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
NSLog("Background task expired by iOS. Cannot run again until a new beacon region event")
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskIdentifier.invalid
})
DispatchQueue.global().async {
while (true) {
let backgroundTimeRemaining = UIApplication.shared.backgroundTimeRemaining
// This will be a very large number if you have proper permissions
// If not, it will generally count down from 10 seconds once you are in the
// background until iOS suspends your app.
NSLog("Thread background time remaining: \(backgroundTimeRemaining)")
Thread.sleep(forTimeInterval: 1.0)
}
}
}

CLLocationManager Region Monitoring: Detect Arrival in Suspended State

I'm looking for a way that I can track that a user has arrived near a designated set of co-ordinates. The functionality needs to work while the application is in the background (preferably within 100 metres). Also, to preserve the battery, I ideally do not want to get too many co-ordinate readings (perhaps a reading every 10 minutes for no longer than a couple of hours).
There are a couple of ways that I have tried to accomplish this task, but have been unable to obtain the desired result:
Background Timer:
I had added a background task in (App.delegate)
func applicationDidEnterBackground(_ application: UIApplication)
Which executed a repeated Timer.scheduledTimer to get co-ordinates and process
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
to detect if the user was within range. This method worked if applied in the short-term, but only until the application was suspended, which was about 3 minutes. Ideally, I would not want to get co-ordinates this frequently.
Region Monitoring:
I had initialised the CLLocationManager as shown below:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .otherNavigation
locationManager.requestAlwaysAuthorization()
}
The LocationManager starts when the application enters into the background:
func applicationDidEnterBackground(_ application: UIApplication) {
self.monitorRegionAtLocation(center: CLLocationCoordinate2D(latitude: x, longitude: y), identifier: id)
locationManager.startUpdatingLocation()
}
Code for monitoring of region:
func monitorRegionAtLocation(center: CLLocationCoordinate2D, identifier: String ) {
// Make sure the app is authorized.
if CLLocationManager.authorizationStatus() == .authorizedAlways {
// Make sure region monitoring is supported.
if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
// Register the region.
let maxDistance = 200.0
let region = CLCircularRegion(center: center,
radius: maxDistance, identifier: identifier)
region.notifyOnEntry = true
region.notifyOnExit = false
locationManager.startMonitoring(for: region)
}
}
}
And I added a didEnterRegion function block for CLLocationManager:
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let region = region as? CLCircularRegion {
let identifier = region.identifier
print("FOUND: " + identifier)
}
}
The code appears to work for detecting entry into a region, however the co-ordinates are not updating while in the background.
Additional Information
I have the Background Modes of Location Updates and Background Fetch enabled
I have supplied values for 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist
The App Settings shows 'Always' permission against the Location
I believe that there has to be a better way of operating these kinds of checks in the background, but I haven't discovered any method of detecting other movements in the background.
Any direction on this matter would be greatly appreciated, and if you need any more information, please let me know and I'll provide what I can.
UPDATE:
I have modified the approach following the advice of comments below to use Region Monitoring.
Any location update/monitoring requires it's location manager to be configured properly so that it can work to the best to provide the desired location update. It's important to check some point when doing background location update:
1. Check background modes of location updates and background fetch should be enable
2. Check 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist should be provided
3. Check if you want to pause in between location update - if yes then you need to provide activity type so that location manager can determine best way to pause location update for you
4. Check if you want to apply distance filter - you want user(device) to move some minimum amount for location manager to send updated location
5. Check if you want desired accuracy- This may cause power drain for certain accuracy type
In your code I can see location manager is configured with some of the parameter but missing accuracy and distance filter for background mode.
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .otherNavigation
Also, if you see pause location update property in Apple doc it says:
For apps that have in-use authorization, a pause to location updates
ends access to location changes until the app is launched again and
able to restart those updates. If you do not wish location updates to
stop entirely, 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.
Essentially it tells that if you want disable pause then you have to keep accuracy level (kCLLocationAccuracyThreeKilometers). Which I guess is missing in your approach.
Also, you can check this link which actually starts background task and then starts location manager monitoring inside the background task.
Hope it helps.
The question says "CLLocationManager Region Monitoring: Detect Arrival in Background". And this is very much possible, but detecting anything after being killed is not possible (from iOS 7).
Whenever user swipe ups your app app-switcher, iOS takes it as the user doesn't wish the app to be running in the background, and so all the call-backs are stopped.
This answer, this answer and this answer also says the same thing. However Apple doc is a little confusing.
My personal observation is that app gets called even in Killed mode but very rarely.
And about getting the location, whenever the delegate method of geofencing is called, you can get location easily.
And the background modes are really not needed for your requirement.
And unfortunately (fortunately for iOS user as they save battery) we don't really have a way to get location just for 1 hr after app being killed.

How to avoid gaps in gps tracking on iOS 11

I am experiencing a problem in a tracking app, but only on iOS 11. The app passively records your GPS position in the background under certain conditions.
The problem that occurs on iOS 11 is that seemingly randomly CLLocationManager stops reporting GPS events for anywhere from 10 to 900+ seconds.
The location manager is set up like the following:
let locationManager = CLLocationManager()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startMonitoringSignificantLocationChanges()
locationManager.desiredAccuracy = 10
locationManager.activityType = .automotiveNavigation
Thinking that the thread CoreLocation is managing and using for all callbacks could be overburden.
I have tried delegating to a different thread for processing, so the app does not tie up CoreLocations resources. This is done using an operation queue that is set up like the following:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .userInitiated
with the callback using that operation queue:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
queue.addOperation {
// process locations
}
}
Introducing the operation queue did not help avoid the gaps, but it did make it so that when the gap occurs the location manager reports a bunch of (different) locations with the same timestamp.
The locations with the same timestamps is not all the missing locations, i.e. if there is a gap of 200 seconds, I might only get 15 locations with the same timestamp.
I am hoping someone here can tell me why this is happening and what I can do to avoid these gaps.
Thanks in advance.
After much review, trials, discussion with Apple we now seem have resolve the issue. Even though Apple will only guarantee GPS tracking the background if the app is in the foreground when the tracking starts. The fix we have applied is changing the following:
locationManager.startMonitoringSignificantLocationChanges()
... to:
locationManager.stopMonitoringSignificantLocationChanges()
locationManager.startMonitoringSignificantLocationChanges()
The theory is that the settings for the app become corrupted when applying true to the "monitoring" setting during startup. If first you set the "monitoring" setting to false and afterwards to true, no corruption occurs.
This fix is live in the app store app for several hundreds of people and tracking in the background is working fine.

Location updates are not delivered on device while application is in background

I'm working on an application that should be able to collect location data while the application is in background.
To allow this, I have done the following:
In the project settings, under the Capabilities tab I have ticked Location updates.
I have set the NSLocationAlwaysUsageDescription key in the Applications Info.plist and have assigned an appropriate string value.
I have configured an instance of CLLocationManager like so:
locationManager.activityType = .Fitness
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = false
Before beginning location collection I call locationManager.requestAlwaysAuthorization(), I have the appropriate delegate callbacks to handle any error.
lastly, I call locationManager.startUpdatingLocation()
When running the application on the simulator, I get a print out of the location updates, and the application's badge is also updated with the number of locations collected, but when running the applications on an actual device, after sending the app in the background, location updates are shortly stopped from being handled by the app (I know this as the badge stops updating).
Am I missing some sort of device specific configuration?
Here is my setting that works all right when the app is in the background or the device in standby mode:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .Other
locationManager.startUpdatingLocation()
I am also using deferred location updates. In didUpdateLocations:
if CLLocationManager.deferredLocationUpdatesAvailable() && deferringUpdates == false {
locationManager.allowDeferredLocationUpdatesUntilTraveled(CLLocationDistanceMax, timeout: deferredLocationTimeout)
deferringUpdates = true
}
and in didFinishDeferredUpdatesWithError:
deferringUpdates = false
Setting the capabilities is not enough as of iOS 9. You need to also set locationManager.allowsBackgroundLocationUpdates = true on your Location Manager before OS can deliver updates to you in the background. It is compulsory to do so according to the documentation. By default it is set to false. See here.

When should I stop updating location manager?

I have an app that makes a call to get the user's location:
-(void)getLocation{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
//SET USER LOCATION
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
self.userLocation = [locations lastObject];
NSLog(#"location in IntroVC %f, %f", self.userLocation.coordinate.latitude, self.userLocation.coordinate.longitude);
}
My question is, because that NSLog keeps spitting out a new location infinitely, when should I stop calling the location? Well I guess its really up to my app's functionality, but doesnt this cause battery drain? If so, I should really look into the best way of stopping the updates.
Your Distance filter of the location manager is set to be kCLDistanceFilterNone. This causes the didUpdateLocations method to be called infinite time.
locationManager.distanceFilter = kCLDistanceFilterNone;
Change this line as
locationManager.distanceFilter = 10;
and try again. Change the value as needed.
So now the didUpdateLocation will not be called infinite times. :)
Hopefully this helps.
Depending on the nature of your app, you may well want to turn location services off when you go into the background or when the screen is locked. These notifications hooks are provided generally in your app delegate file (.m). Yes you are right, location services significantly drain battery and it a highly recommended practice for iOS apps using location services to use it cautiously.
Apple seems to have thought about this and has provided an API that notifies the app only if the user has moved "significantly" Apple the significant change location service. The definition of "moved significantly" varies on various aspects depending on WiFi availability, cell tower availability, GPS availability etc. Luckily all this is obfuscated within this API.
I call this method after updating the location in didupdateloactions, so when my app goes in background it remove location icon from top status bar.

Resources