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.
Related
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.
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.
I have followed the apple developer guide on location services.
I have included the info.plist key value pair:
key: Privacy - Location Always Usage Description
value: The application myTestApp needs access to location services even in the background
I have created an instance of CLLocationManager as a class variable of the view controller:
var locationManager = CLLocationManager()
I have code in the view controller viewDidLoad() function that assigns the delegate and checks for the current status:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
let status = CLLocationManager.authorizationStatus()
switch status {
case .denied, .restricted, .notDetermined, .authorizedWhenInUse :
print("The switch detected a state other than always")
locationManager.requestAlwaysAuthorization()
default:
print("Services Authorized")
}
Yet, when I build and run the application after making small changes it does not prompt for user access, and even worse, when it does sometimes work it thinks the user is in the middle of the Atlantic ocean at Lat: 0.0 and Long: 0.0.
Is there anything outside of the code I need to do so that I can recreate the user experience of authorizing the application and then seeing it zoom to a real location?
The user permission prompt is one time only. Once it is allowed,
device would fetch user location automatically from that point.
Try setting the custom location in the simulator via these two methods.
1. Using the simulator menu actions
2. Using the Xcode debugger options
The simulator handles location a bit differently from the device. I'd suggest pushing the app over to the device itself and running from there to see how it asks for permission and make sure it is giving the right location. It's very easy to do and very helpful for testing with apps that use access to location services.
https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/LaunchingYourApponDevices/LaunchingYourApponDevices.html
Essentially, plug phone in, select it from the list of devices to simulate with, press play and accept your developer access on the phone to run the app from your device.
I am writing (in Swift) an iOS 9 app that needs location updates including in background mode. The problem is that I only get location updates in foreground. I think I have done everything correctly:
In info.plist:
Specified a value for NSLocationAlwaysUsageDescription.
Specified a value for NSLocationUsageDescription.
Specified a value for NSLocationWhenInUseUsageDescription.
Included "location" in UIBackgroundModes.
Included "gps" and "location-services" in UIRequiredDeviceCapabilities
Started the location manager as follows:
let locationManager = CLLocationManager()
locationManager.desiredAccuracy =
kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 250.0
locationManager.allowsBackgroundLocationUpdates = true
locationManager.startUpdatingLocation()
I have also verified that
locationManager.pausesLocationUpdatesAutomatically is false.
I get no location updates while the app is not in the foreground. The only time I do get location updates is if I bring the app to foreground, and even then, not all the time.
Any idea what's going wrong? Or my expectations incorrect?
How are you sending the app to the background? If you swipe to kill then your app won't automatically restart to receive the location events. I don't see it in your code, did you call requestAlwaysAuthorization? You might also want to implement LocationManager:didFailWithError just to see if that gives you any ideas.
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.