Significant Location updates after force quit in iOS app - ios

I am currently tracking the user location updates for every mile travelled. And I have background mode turned on for app to look for location updates.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
locationManager.delegate = self
if iOS8 {
locationManager.requestAlwaysAuthorization()
} else {
locationManager.startUpdatingLocation()
locationManager.stopUpdatingLocation()
}
locationManager.distanceFilter = 1609.34 // meters in 1 mile
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
return true
}
But, I am wondering, if the application has been force quit, I would still like for the application to be updating the location. Is that possible with startUpdatingLocation class? Or should I be using startMonitoringSignificantLocationChanges
I read the doc here but didn't quite understand when to move from startUpdatingLocation to startMonitoringSignificantLocationChanges when/while the app is being force quit. Should it be under applicationWillTerminate function ?
Or if that is even possible or is there something else i should be doing.
UPDATE:
I read here
In most cases, the system does not relaunch apps after they are force
quit by the user. One exception is location apps, which in iOS 8 and
later are relaunched after being force quit by the user. In other
cases, though, the user must launch the app explicitly or reboot the
device before the app can be launched automatically into the
background by the system.
If thats the case, should I use startUpdatingLocation or move to startMonitoringSignificantLocationChanges ?

If the app was terminated, you could not run any program for the app.
And it's not always to handle applicationWillTerminate function when the app was terminated. In some situations, the system kills the app without any notification. Please read the document about application life cycle.
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html
I think you want to do your app is running like a daemon service. iOS does not allow us that unless the device is jail broken.
https://www.chrisalvares.com/blog/7/creating-an-iphone-daemon-part-1/
If you are care of the device battery, you would to do like this.
startUpdatingLocation and stopMonitoringSignificantLocationChanges at applicationWillEnterForeground.
startMonitoringSignificantLocationChanges and stopUpdatingLocation at applicationDidEnterBackground.

Related

Continuous location update background iOS13

I am currently testing the background location mode in iOS 13, as I want to track the user´s location and motion (using the CMMotionManager) in the background.
Therefore, I have my own (singleton) class handling the location tracking. I initialize the CLLocationManager in the following way:
func initializeLocationManager() -> CLLocationManager {
let manager = locationManager ?? CLLocationManager()
manager.delegate = self
manager.requestAlwaysAuthorization()
manager.allowsBackgroundLocationUpdates = true
manager.pausesLocationUpdatesAutomatically = false
manager.distanceFilter = kCLDistanceFilterNone
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.activityType = .other
return manager
}
Then I start the following services:
func startLocationServices() {
// ...
locationManager.startUpdatingLocation()
locationManager.startMonitoringVisits()
locationManager.startMonitoringSignificantLocationChanges()
// ...
}
In addition, I implemented the CLLocationManagerDelegate-methods didChangeAuthorization(), didUpdateLocation().
In the info.plist-file I appended the following entries:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>...</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>...</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
In my ViewController, I call the startLocationServices.
Currently, I set the app's authorization to track location data to ".authorizedAlways"
The location updates stop after approximately 60 - 130 minutes.
To solve the problem, I already added the didFinishLaunchingWithOptions-function, which triggers the location-updates again:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let launchOptions = launchOptions,
let isLocationKey = launchOptions[UIApplication.LaunchOptionsKey.location] as? Bool,
isLocationKey {
restartServices()
}
return true
}
When the app gets awoken using this function, I managed to get continuous data on some tests, but sometimes the app was suspended again after a few minutes.
Last, I also tried a timer that restarts the location tracking every 5 minutes, but this does not seem to affect the update-duration at all.
So my question is if there is a way of continuously receiving the location updates in the background, or is there some option I am missing?
Thanks in advance.
Edit: I tested the application on iOS 12 and it gets continuous updates in 5/5 tests.
So I guess the problem is related to iOS 13.
Nothing is wrong with code !! ,
I have faced the same issue and after research I found that
At the WWDC19 keynote Apple announced two changes in the way location permissions will work in iOS 13. The first change gives users the option to share their location with your app just once. This makes it easier to try out location features and helps users keep sensitive location data private.
The first notable change is that even when you call requestAlwaysAuthorization, the user will only get the ‘just now’ and ‘when in use’ options in the permission dialog. If the user grants you ‘when in use’ permission and you try to scan for location in the background, only then the user will be presented a dialog to grant the background permission.
So When user grants WhenInUseUsage permission You will get always in CLAuthorizationStatus and If user choose Allow Once CLAuthorizationStatus will be rested to notDetermined once app launches again
You can check this article for detailed info
https://medium.com/q42-engineering/apple-location-permission-ios13-1e0e59002889
And Here is video https://developer.apple.com/videos/play/wwdc2019/705/
EDIT
After When In User permission granted by the user iOS will show user a another dialogue after some days to change permission from when in use to always allow.
So now there is no way to directly ask user for always allow permission instantly like we do before.
This is an iOS 13 bug. The app permissions get messed up by iOS. While it shows "Always" in settings, it actually behaves more like "While in Use". One app is put to sleep in background while other keeps getting locations on the same device (in background).
To solve it, goto Settings-> Search your app with issue -> Location -> Change Always to Never and then back to Always.
Unfortunately, there is nothing, you can do in the code to fix this.

How to get Location coordinates when app in background mode at the same time suspend mode

I used loc_manager.startMonitoringSignificantLocationChanges() but didUpdateLocations not calling in foreground ,background and suspend mode can anyone help me please,
override func viewDidLoad() {
super.viewDidLoad()
loc_manager.requestAlwaysAuthorization()
loc_manager.allowsBackgroundLocationUpdates = true
loc_manager.delegate = self
loc_manager.desiredAccuracy = kCLLocationAccuracyBest
loc_manager.pausesLocationUpdatesAutomatically = false
loc_manager.distanceFilter = kCLDistanceFilterNone
}
For Location in foreground
In the foreground the CLLocationManager object and delegates will do fine. This will also work with the 'while in use' location permission.
For Location in background
Please enable the Background Modes from the capabilities of the project and enable the 'Location updates'. After enabling this, the only configuration to get the updates in the background(not in killed state) is to set'allowsBackgroundLocationUpdates' to true(which you have done already).
For more information on getting location in the background follow :
https://developer.apple.com/documentation/corelocation/cllocationmanager/1620568-allowsbackgroundlocationupdates
For location in application suspended or killed
Here the significant location changes are only needed when you want to get the location when the application is killed by the user(or Suspended). This significant location change will launch the application in background and read the location of the device. This mode needs 'Always authorised' location permission.
For significant location changes while the application is in killed state, follow below link. This is in objective C but it can be easily done in swift also.
http://mobileoop.com/getting-location-updates-for-ios-7-and-8-when-the-app-is-killedterminatedsuspended
Hope this helps.

Location tracking stops after a while when app is in the background

I've created a simple app which tracks user location and creates local notification for every time location is updated.
I enabled the background modes below,
let locationManager = CLLocationManager()
open override func viewDidLoad() {
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.distanceFilter = 10
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
}
open func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let notification = UILocalNotification()
notification.alertBody = "location updated"
notification.fireDate = Date()
UIApplication.shared.scheduleLocalNotification(notification)
}
I set string for NSLocationAlwaysUsageDescription and ask for permission. User grant permission for always usage when the app loaded first time.
It's working well when app is in the foreground, when it goes background still working at least in 5-40 minutes time range which is changeable
by battery or other opened apps.
The problem is why it stops working, Isn't it expected to be keep working?
I've never seen a time limit in Apple docs.
Switch to significant location updates when the app moves to background. iOS will unload the app if it keep alive in the background indefinitely.
locationManager.pausesLocationUpdatesAutomatically = false
Reduce Accuracy
Set the desiredAccuracy property of the location manager object
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
You can use one of the CLLocationAccuracy constants
IMPORTANT
By default, standard location updates on iOS devices run with an accuracy level of best. Change these settings to match
your app’s requirements. Otherwise, your app will unnecessarily waste
energy.
Auto-Pause
Set the pausesLocationUpdatesAutomatically property of the location manager object to true
self.locationManager.pausesLocationUpdatesAutomatically = true
IMPORTANT
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.
Allow background updates
Set the allowsBackgroundLocationUpdates property of the location manager object to true
self.locationManager.allowsBackgroundLocationUpdates = true
Apps that want to receive location updates when suspended must include
the UIBackgroundModes key (with the location value) in their app’s
Info.plist file and set the value of this property to true. The
presence of the UIBackgroundModes key with the location value is
required for background updates
Specify an Activity Type
Set the activityType property to let Core Location know what type of location activity your app is performing at a given time
self.locationManager.activityType = .automotiveNavigation
You can use one of the CLActivityType cases
Defer Location Update
On supported devices with GPS hardware, you can let the location
manager defer the delivery of location updates when your app is in the
background. For example, a fitness app that tracks the user’s location
on a hiking trail can defer updates until the user has moved a certain
distance or a certain period of time has elapsed.
Energy Efficiency Guide for iOS Apps - Location Best Practices
GettingLocationWhenSuspended
After searching for references (talking about any limitation), I assume that Apple Core Location Best Practices video session could be useful! at 06:53 talking about standard location in the background:
furthermore, Core Location won't take any action to ensure your app
continues to run, so if you have background run for some reason and
you decide to start a location session you might get some updates, but
you might also get suspended before you receive all information that
you hope to receive...
Actually, I faced this issue before, -as a workaround- the core location was used to keep tracking the location of the user to do unrelated functionality to its location -which is uploading files-, but this workaround didn't work since iOS 9 has been released; I even posted a question referring to this issue.
However, it seems your case is not identical to what I faced, if you are aiming to:
... creates local notification for every time location is updated.
then you might need to follow the approach of integrating with User Notification Framework - UNLocationNotificationTrigger:
The geographic location that the user must reach to enable the
delivery of a local notification.
It is also mentioned in the video session (08:59).
Probably, this is could be not what are you looking for, but since we have no guarantee that the background execution will continue running, you might -somehow- find a way to integrate it in your app to achieve the desired functionality.
Update for iOS 11:
You might need to check this answer for the proper way to request the location access.
By the sound of it the app is being killed due to memory constraints.
It should however be re-launched when a new location becomes available, as described here: https://developer.apple.com/documentation/uikit/uiapplicationlaunchoptionskey/1623101-location
You should see application(_:didFinishLaunchingWithOptions:) being called, and the 'location' key should be present in the launch options. You'll can then re-create whatever is consuming the locations and continue recording.
If it's not being re-launched it could be too memory hungry. Check the memory consumption of the app and see if applicationDidReceiveMemoryWarning(_:) is being called.
I assume you haven't implement background task. You can read here.
In the above link under section "Implementing Long-Running Tasks" point no. 3 is your situation, so it's valid you can use background location update in your project and for same you need to implement a background task too.
There are three way to track user location(as per above link under section "Tracking the User’s Location" ) :-
Foreground-only location services (which works in your case)
The significant-change location service (Recommended), but I think it is not usable in your case as you want to update user location per 10 meter and it works for ~500 meters, for more please see here
Background location services (I think you are trying for this) and solution is to add a background task.
Below is example of background task and you can modify as per your requirement, it works for me since last 2 hours and my app still update location in background.
.
In your AppDelegate class please update below function and then run your app in background.
func applicationDidEnterBackground(_ application: UIApplication) {
application.beginBackgroundTask(withName: "") {}
}
And below is my ViewController class
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self;
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.allowsBackgroundLocationUpdates = true
locationManager.distanceFilter = kCLDistanceFilterNone
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
self.locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("didUpdateLocations \(Date())")
self.locationManager.stopUpdatingLocation()
}
}
I ran into this where a QT app would stop getting location events in the background after an hour or two.
In the App Delegate when the app would go into the background I would stop the CLLocationManager, decrease the Accuracy from kCLLocationAccuracyBest to kCLLocationAccuracyNearestTenMeters AND increase the distance filter from None to 50 meters and it would then track for 24+ hours.

CoreLocation - Geofence event to start location services

I'm using CoreLocation in my iOS Application. I'm using Significant Location changes and some Geofences. It's currently working well but I would like to improve my location data quality with getting more location updates.
Currently I have one CLLocationManager instance running in my application. This single instance is listening geofence and locationUpdates.
As far as I know, significant changes are related to cell tower changes which is enough for me but I have some cases that I need more location data.
I don't want to use startUpdatingLocation as it increases the battery consumption but I have an idea about enabling and disabling locationUpdates (while keeping the significant updates open) between the geofences. So, I'm planning to start a secondary instance of CLLocationManager when a geofence event occurs. I'll keep that secondary instance running until another Geofence event occurs.
This will help me to get more detailed location data where I need without keeping location services always open.
My question is I don't know if my approach is possible in iOS. In any event of geofence, my app will most like be on background. Can I still enable the secondary CLLocationManager start and stop?
I already enabled Background Modes for Location Updates, it's working fine on the current version.
Thanks in advance.
I don't think you need separate CLLocationManager instances. My understanding (and experience) is that geofencing and location updates can coexist in the same CLLocationManager.
Your app can respond to location events in the background, e.g.:
In AppDelegate.swift:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
// were we invoked due to LocationManager event:
if launchOptions?[UIApplicationLaunchOptionsLocationKey] != nil {
printLog( "UIApplicationLaunchOptionsLocationKey" )
// We were previously terminated/suspended and need to configure locmgr
// etc.
// after this, app will process received location events via
// didUpdateLocations()
// didExitRegions()

Send location updates every 30 minutes in Swift

I have background mode on for location services and aiming to send out location (latitude and longitude) to the server every 30 minutes. For now I am printing the same in the console. It seems to work for a while but I am wondering how do I work with NSTimer in this case. And from where should I be calling it?
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var locationManager = CLLocationManager()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(application: UIApplication) {
// 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.
}
func applicationDidEnterBackground(application: UIApplication) {
// 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.locationManager.delegate = self
self.locationManager.startUpdatingLocation() // I know i should be using signification location option here. this is just for testing now.
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
self.sendBackgroundLocationToServer(newLocation);
}
func sendBackgroundLocationToServer(location: CLLocation) {
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
UIApplication.sharedApplication().endBackgroundTask(bgTask)
}
println(location.coordinate.latitude)
if (bgTask != UIBackgroundTaskInvalid)
{
UIApplication.sharedApplication().endBackgroundTask(bgTask);
bgTask = UIBackgroundTaskInvalid;
}
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
application.beginBackgroundTaskWithExpirationHandler{}
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
application.beginBackgroundTaskWithExpirationHandler{}
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
application.beginBackgroundTaskWithExpirationHandler{}
}
}
Maybe calling application.beginBackgroundTaskWithExpirationHandler{} is a bad idea? What options do I go with here?
The idea of beginBackgroundTask... is to start a finite length task so that if the user leaves the app, it will keep running in the background task for some short, finite period of time (3 minutes, I believe). And before the time runs out, you have to call endBackgroundTask or else the app will be summarily terminated.
So, sadly, the background task mechanism is not really suited for your desired intent. There are, though, a narrow set of special background modes designed for continued background operation outside a narrow set of functions (VOIP, audio, etc.). For more information, see the Implementing Long-Running Tasks section of the App Programming Guide for iOS: Background Execution.
Now, one of those background modes is for a "location" service. So, if that is a central feature of your app, essential for proper function, then you can register for the location background mode, and your app will continue to run in the background. From there, you can monitor for location updates, and if a sufficient amount of time has elapsed, trigger some process. But if this background location mode is not an essential feature of your app, Apple is likely to reject your app for requesting a background mode that it doesn't need.
By the way, you should be aware that starting standard location services may drain the device battery. You might consider using the battery efficient "significant change" location service. This also has the virtue of automatically waking your app every time the user moves some significant distance (e.g. measured in km; I believe it's triggered by moving to different cell tower).
Couple of notes, since we've been fighting the location issues and struggling with background applications that don't seem to wake us up.
NSTimer is only good as long as your application is not suspended. Sure, it's in the background, but onlyinasmuchas it is capable of receiving notifications from the iOS (APN, location, ...). Eventually, your timer WILL stop firing while you run in the BG. So, you are relying on one of the BG modes to kick you.
If you ARE using CLLocationManager.startUpdatingLocation, you're going to notice that you quit getting those after a bit. Especially when the phone relaxes and tries to sleep. This is because of a poorly documented feature called CLLocationManager.pausesLocationUpdatesAutomatically ... which decides to stop sending those when set to "true" (the DEFAULT setting). You can set this to "false", and you'll continue to get your position updates. Which will pretty much make sure your app does what you are wanting.
As noted above, you must make sure when you get your locationManager.didUpdateLocations callback that you begin a backgroundTask to keep your application working while you process that information and send your network data. But you seem to understand that.
Just waking up long enough to "record" a location update isn't so bad. Just make sure you don't spawn your network code unless you've clearly hit your 30 min expectation.
try this:
Make a singleton for Location Service. Registered it with a NSNotification
In AppDelegate's willEnterBackGround, you send a notification via NSNotificationCenter
then, your singleton will start updatingLocation and send it to server when location's data received.

Resources