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()
Related
I'm working on a navigation application, everything working in terminated, background and fore ground state.
But in one scenario of terminated state startMonitoringSignificantLocationChanges is not handling itself.
The issue is
when i start the startMonitoringSignificantLocationChanges and killed the app, then I'm getting location event like after 0.5-1km because of that it draws straight line from my initial position to the first location event I get.But when the location event starts coming then everything work smoothly
Same issue occur again when in the middle of travelling I open the application to check my route status and then kill the application, again location events start coming after 0.5-1km and a straight line was drawn.
The code is straight
significantLocationManager = CLLocationManager()
significantLocationManager?.allowsBackgroundLocationUpdates = true
significantLocationManager?.pausesLocationUpdatesAutomatically = false
significantLocationManager?.requestAlwaysAuthorization()
and call the tracking when user needs by
significantLocationManager?.startMonitoringSignificantLocationChanges()
Rest I have handled the incoming location event in the app delegate to save in db.
So question is how should I handle this scenario in which straight line is drawn ?
From Apple documentation:
Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes. If the device is able to retrieve data from the network, the location manager is much more likely to deliver notifications in a timely manner.
If you need to receive location updates as soon as possible I'd recommend to use startUpdatingLocation() with desired distanceFilter of CLLocationManager.
You can use Location update in background mode. From Apple documentation:
When you start the significant-change location service, a recently
cached value may be reported to your delegate immediately. As new
location data is obtained, the location manager calls your delegate's
locationManager(_:didUpdateLocations:) method with the updated values.
The locations parameter always contains at least one location and may
contain more than one. Locations are always reported in the order in
which they were determined, so the most recent location is always the
last item in the array, as shown in Listing 2.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
// Do something with the location.
}
Here you will get the last cached location in your device, and it should be very precise if you have location service turned on in your device of course.
Another thing to know is this. Note form Apple:
The significant-change location service requires authorization. For
more information Requesting Authorization for Location Services.
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.
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.
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.
I am failing to find any documentation on this. If my application is able to use background location services, how much processing can it really do before the device stops the task as its using too much resource?
E.g. When the location changes, I would like to ping my server with the new location.
Regards:
John
If an app has the permission of getting location updates in the background, then you are not limited on performing any tasks, so you could run any code, as the app is allowed to execute code while it is suspended (this is used by apps providing navigation).
However, be aware, that Apple will not approve your App, with the background Location updates permission, unless it's an App that would justify this requirement (Sports tracking App, Navigation App, etc)
You can get location updates from Region Monitoring or Significant Location changes, without having to use the location updates background mode.
So now comes the tricky part. Do I have enough time to perform a Server update of the users Location when my App is started from Background mode by the Region Monitoring or Significant Location services ?
The answer, seems to be yes, as suggested by this article
I haven't tried this, but judging by the code, I don't find a reason why this shouldn't work!
You will have to declare App’s Supported Background Tasks in info.plist
Under the information property list add array given format below:
Inforrmation Property list (Dictionary) ->
Required background modes (array) (1 item) ->
item 0 (String) - App registers for location updates
after these settings in info.plist you can do code which you want to do in background process at AppDelegate:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
/*
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.
*/
self.locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
[_locationManager stopMonitoringSignificantLocationChanges];
[_locationManager startUpdatingLocation];
}
For more information you can visit:
Declaring Your App’s Supported Background Tasks