33

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,

enter image description here

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.

Okan Kocyigit
  • 13,203
  • 18
  • 70
  • 129
  • And for authorization, you requested `requestAlwaysAuthorization()`, right? – Ahmad F Aug 11 '17 at 15:33
  • @AhmadF, yes "Allow 'app' to access your location even when you are not using the app?" was prompted. And I've allowed, and in iPhone privacy settings it says "always" for locations services for the app, no doubt . – Okan Kocyigit Aug 11 '17 at 15:37
  • @ocanal : just refer this url https://stackoverflow.com/questions/6347503/how-do-i-get-a-background-location-update-every-n-minutes-in-my-ios-application – Bhadresh Sonani Aug 17 '17 at 13:16
  • @ocanal you might need to check [this answer](https://stackoverflow.com/a/46545696/5501940) to make sure that you are setting the appropriate keys for what are you aiming to achieve; I already updated my answer, hope it helps :) – Ahmad F Oct 04 '17 at 07:23

6 Answers6

37

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
Vimal Saifudin
  • 1,815
  • 1
  • 21
  • 28
  • This seems to be a good solution, it's also been discussed on [apple developer form](https://forums.developer.apple.com/message/200336). I'm testing it and see whats going on. – Okan Kocyigit Aug 15 '17 at 08:59
  • 1
    As my test results, this works for my project. Bu I've to correct the term 'significant location', this isn't 'significant location' that's a different thing which Anand Suthar has already said something about that. – Okan Kocyigit Aug 21 '17 at 15:10
  • 5
    @ocanal FYI without this, it stops roughly after 17 (others have mentioned this in their questions before. I've verified it) minutes of no movement. Your 5-40 is a result of moving, other if you stay put, its ~17 minutes. If you set `pausesLocationUpdatesAutomatically` to false, it could drain your battery. **FYI it could only be resumed if location is paused when app is in foreground**, otherwise you can't do anything to resume it in background. Documentation is VERY poor on this. – mfaani Aug 21 '17 at 20:21
  • 3
    @ocanal I recommend to set a timer e.g. after 5 minutes of -100meters of movement, degrade the Accuracy to OneKilometer/ThreeKilometer. Before degrading your accuracy, save the last location...then using another timer: every 5/10 minutes **momentarily** upgrade accuracy to whatever you like and compare that location with the location you saved. If you didn't move too much (e.g. 100Meters) then stay degraded, otherwise upgrade accuracy. (The time and meters varies based on your business requirements. You may or may not want to have a degraded accuracy for too long) – mfaani Aug 21 '17 at 20:21
  • @ocanal I am using standard location service with distance filter applied to track user locations in background. Since the answer has been accepted, I would like to know if disabling location updates pauses makes the app track the location continuously? – Sujal Jul 03 '18 at 04:57
  • @Sujal, We've already published an app to appstore, which is based on using socket.io and tracking location changes, `pausesLocationUpdatesAutomatically = false` drains the battery, but that's not really important for us, it seems to be working very well, ios doesn't unload the app. But we've to keep track changes on newer versions and see what is going on. that would be very good if you share your experience and i will do also. – Okan Kocyigit Jul 03 '18 at 06:30
  • @ocanal Battery usage is not prime concern for the app as of now. The target is to get location update and run a timer after receiving each update to perform some task. I am doubtful if the app stops getting location updates when in background on the long run. So its working fine for you? This is the first time i am using location services. I have tried other approaches like visit monitoring services but faced some issues (https://stackoverflow.com/questions/51012580/properly-detect-all-visits-using-visit-monitoring-service) – Sujal Jul 03 '18 at 06:47
  • 1
    @Sujal, yes I've already tested it for 5 hours, app didn't unload and i keep location tracking for 5 hours without any problem. But things are continiously changing on ios side, so you should control for newer versions like ios 12, as my test result it's fine for ios 11. – Okan Kocyigit Jul 03 '18 at 07:25
  • Using pausesLocationUpdatesAutomatically = false solved a problem for me where I was losing tracking (in this case after leaving the device stationary for ~20 minutes, not even using any other app. To combat battery drain, I'll experiment with CoreMotion activity types and if .stationary turn off location updating until the activity type changes. – Stonetip Sep 08 '18 at 04:16
14

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.


Adrian Bobrowski
  • 2,681
  • 1
  • 16
  • 26
  • thanks for detailed explanation. I didn't mention about my app but as I commented on [Anand Suthar's answer](https://stackoverflow.com/questions/45637922/location-tracking-stops-after-a-while-when-app-is-in-the-background/45744821#comment78414898_45710503) this is similar to navigation app, so I can't reduce the accuracy and if I set `pausesLocationUpdatesAutomatically` as true it will stop after a while that's why I'm asking this question. So your answer is for who wants to create an energy-friendly app. But thanks again for the details. – Okan Kocyigit Aug 17 '17 at 21:08
  • @ocanal I read your comments. And I have one question. Do you try defer location update – Adrian Bobrowski Aug 17 '17 at 21:11
  • Sorry I have referred to another comment, [this](https://stackoverflow.com/questions/45637922/location-tracking-stops-after-a-while-when-app-is-in-the-background/45744821#comment78254517_45639515) was the comment I explained. – Okan Kocyigit Aug 17 '17 at 21:14
  • what do you mean by deferring? – Okan Kocyigit Aug 17 '17 at 21:14
  • @ocanal IMO. iOS gets you access to your location because your app consumes batteries – Adrian Bobrowski Aug 17 '17 at 21:24
3

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.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
  • thanks for video references and searching. Actually local notification is just used for debugging. That's not what I'm working on. I'm working for a taxi driver app, which needs to get driver location and send it to server even app is in the background. I've created a socket connection and sending data to server through it. So it seems we have no guarentee that we keep socket connecion live when app goes background. Is there any event which is called when app kill my socket connection and location update or maybe clear my instances? – Okan Kocyigit Aug 12 '17 at 08:06
  • Actually, I am not sure about that... You might want to check what's the appropriate background execution to your case, hope you find a solution for your case. – Ahmad F Aug 14 '17 at 06:21
  • @ocanal Did you ever find a solution that was able to keep your socket connection alive?? – illis69 May 15 '18 at 19:56
  • @illis69 yes the accepted answer working for me, I've already tested and published project. It has been working well. – Okan Kocyigit May 16 '18 at 06:52
1

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.

Tom Durrant
  • 166
  • 1
  • 4
  • I'm debugging right now. I've put breakpoint for the events, app stopped location updating after 24 minutes, but `applicationDidReceiveMemoryWarning` is not called and app is not killed still working and debugging is still working. I'm waiting for `application(_:didFinishLaunchingWithOptions:)` to see if it's called again. – Okan Kocyigit Aug 11 '17 at 15:42
  • hımm, actually app will never relaunch, because it's still working :) – Okan Kocyigit Aug 11 '17 at 15:55
1

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" ) :-

  1. Foreground-only location services (which works in your case)

  2. 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

  3. 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()
    }

}
Community
  • 1
  • 1
Anand Suthar
  • 3,678
  • 2
  • 30
  • 52
  • What is that timer for? Why do we need to call `self.locationManager.startUpdatingLocation()` every second? – Okan Kocyigit Aug 17 '17 at 07:06
  • I am using timer for my testing purpose, It is for updating user location per/second. You can use your code instead. The main thing is background task -> which you need to add in AppDelegate class. – Anand Suthar Aug 17 '17 at 07:09
  • @ocanal "background task" didn't work for you ?, but I am able to run the app in background for infinite time and also track user location without any issue. – Anand Suthar Aug 19 '17 at 06:03
  • 1
    I'll also try this one but I need to test it at least 4-5 hours to see if it works. But on [apple documentation](https://developer.apple.com/documentation/uikit/uiapplication/1623051-beginbackgroundtask) it says "Apps running background tasks have a finite amount of time in which to run them. (You can find out how much time is available using the backgroundTimeRemaining property.) If you do not call endBackgroundTask(_:) for each task before time expires, the system kills the app." – Okan Kocyigit Aug 19 '17 at 06:33
  • Yes, you need to implement a finite time task, you can read this in first link I post in answer, as you can use "[application endBackgroundTask:bgTask]" and "bgTask = UIBackgroundTaskInvalid". – Anand Suthar Aug 19 '17 at 06:47
1

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.

self.name
  • 2,341
  • 2
  • 17
  • 18