93

I'm writing an application that requires background location updates with high accuracy and low frequency. The solution seems to be a background NSTimer task that starts the location manager's updates, which then immediately shuts down. This question has been asked before:

How do I get a background location update every n minutes in my iOS application?

Getting user location every n minutes after app goes to background

iOS Not the typical background location tracking timer issue

iOS long-running background timer with "location" background mode

iOS full-time background-service based on location tracking

but I have yet to get a minimum example working. After trying every permutation of the above accepted answers, I put together a starting point. Entering background:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    self.bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"ending background task");
        [[UIApplication sharedApplication] endBackgroundTask:self.bgTask];
        self.bgTask = UIBackgroundTaskInvalid;
    }];


    self.timer = [NSTimer scheduledTimerWithTimeInterval:60
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
}

and the delegate method:

- (void)locationManager:(CLLocationManager *)manager 
    didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {

    NSLog(@"%@", newLocation);

    NSLog(@"background time: %f", [UIApplication sharedApplication].backgroundTimeRemaining);
    [self.locationManager stopUpdatingLocation];

}

The current behavior is that the backgroundTimeRemaining decrements from 180 seconds to zero (while logging location), and then the expiration handler executes and no further location updates are generated. How do I modify the above code in order to receive periodic location updates in the background indefinitely?

Update: I'm targeting iOS 7 and there appears to be some evidence that background tasks behave differently:

Start Location Manager in iOS 7 from background task

mfaani
  • 33,269
  • 19
  • 164
  • 293
pcoving
  • 2,770
  • 1
  • 21
  • 17

9 Answers9

62

It seems that stopUpdatingLocation is what triggers the background watchdog timer, so I replaced it in didUpdateLocation with:

[self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers];
[self.locationManager setDistanceFilter:99999];

which appears to effectively power down the GPS. The selector for the background NSTimer then becomes:

- (void) changeAccuracy {
    [self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [self.locationManager setDistanceFilter:kCLDistanceFilterNone];
}

All I'm doing is periodically toggling the accuracy to get a high-accuracy coordinate every few minutes and because the locationManager hasn't been stopped, backgroundTimeRemaining stays at its maximum value. This reduced battery consumption from ~10% per hour (with constant kCLLocationAccuracyBest in the background) to ~2% per hour on my device.

pcoving
  • 2,770
  • 1
  • 21
  • 17
  • 4
    Could you update your question with the full working example? Something like: "UPDATE: I figured it out. Here's my solution..." – smholloway Oct 01 '13 at 18:40
  • @Virussmca I reverted to a previous answer that is much more robust with similar performance. Stopping location updates altogether seems to trigger a race condition (or some other non-deterministic scenario). – pcoving Oct 30 '13 at 17:40
  • This approach will not work as you are expecting : 1) Increasing the value of distanceFilter property doesn't result in battery saving because gps still has to keep figuring your location. 2) In iOS 7 background time is non - contiguous. 3) You might take into account significantChangeLocation services. 4) Apart from your soution in iOS 7 you can't start UIBackgroundModes - location from background... – aMother Nov 03 '13 at 16:45
  • 2
    @aMother 1) I realize that increasing the distance filter (and not processing callbacks) doesn't save much battery. However, setDesiredAccuracy does! It falls back on cell tower information which comes basically for free. 2) Non-contiguous? 3) I've specified, in bold, that I require high accuracy. Significant location changes does not provide this. 4) Location services are not being started/stopped from the background. – pcoving Nov 04 '13 at 04:01
  • @pcoving Have you tried to call stopUpdatingLocation, then startMonitoringSignificantLocationChanges instead of changing the desired accuracy, and when you want something more accurate call stopMonitoringSignificantLocationchanges and then startUpdatingLocation? It could save more battery if it works. – François Marceau Nov 14 '13 at 03:28
  • I love the idea of changing setDesiredAccuracy to shut down the GPS. I have been trying to figure this out for the last couple of days and this is the first workable solution I have been able to implement. Have you deployed this on an actual device and does the battery performance hold at ~2% per hour ? I will be test driving mine tomorrow. – Tehsin Dec 12 '13 at 23:50
  • Iam searching for something like CLActivityTypeAutomotiveNavigation or CLActivityTypeFitness but only vice versa. That means location/speed change recognition from automotive to running/walking in background mode plus the ability to activate some background jobs like gps for best accuracy. Searched a lot but did not found anything for my problem – verklixt Aug 18 '14 at 09:31
  • I want to upvote this answer again! I have been battling with this problem for a long time and the steps here have made a massive difference to my application. Thank you! – Gary Wright Apr 14 '15 at 12:07
  • 2
    I know this is an old post now, but users of my application that use this trick have noticed a significant increase in the battery usage since upgrading to iOS9. I haven't investigated yet, but I have a feeling that this 'trick' may no longer work any more? – Gary Wright Oct 14 '15 at 07:30
  • Anyone know if, as of iOS 10, this is still a valid solution? I have an app eating battery, even though stopUpdatingLocation is called. It's maddening that this is so poorly documented in Apple's documentation (unless I'm missing something). Thanks. – Mario May 08 '17 at 20:40
  • I'm guessing an important requirement for this to work is **if** you only have **in-use** authorization then you shouldn't have `pausesLocationUpdatesAutomatically` set to `true` otherwise once it's paused there is no way to startLocation updates again right? – mfaani Jul 19 '17 at 18:39
  • Thanks! Works well on iOS 11.2 – Alex Mar 20 '18 at 09:15
46

I did write an app using Location services, app must send location every 10s. And it worked very well.

Just use the "allowDeferredLocationUpdatesUntilTraveled:timeout" method, following Apple's doc.

Steps are as follows:

Required: Register background mode for update Location.

1. Create LocationManger and startUpdatingLocation, with accuracy and filteredDistance as whatever you want:

-(void) initLocationManager    
{
    // Create the manager object
    self.locationManager = [[[CLLocationManager alloc] init] autorelease];
    _locationManager.delegate = self;
    // This is the most important property to set for the manager. It ultimately determines how the manager will
    // attempt to acquire location and thus, the amount of power that will be consumed.
    _locationManager.desiredAccuracy = 45;
    _locationManager.distanceFilter = 100;
    // Once configured, the location manager must be "started".
    [_locationManager startUpdatingLocation];
}

2. To keep app run forever using "allowDeferredLocationUpdatesUntilTraveled:timeout" method in background, you must restart updatingLocation with new parameter when app moves to background, like this:

- (void)applicationWillResignActive:(UIApplication *)application {
     _isBackgroundMode = YES;

    [_locationManager stopUpdatingLocation];
    [_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [_locationManager setDistanceFilter:kCLDistanceFilterNone];
    _locationManager.pausesLocationUpdatesAutomatically = NO;
    _locationManager.activityType = CLActivityTypeAutomotiveNavigation;
    [_locationManager startUpdatingLocation];
 }

3. App gets updatedLocations as normal with "locationManager:didUpdateLocations:" callback:

-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
//  store data
    CLLocation *newLocation = [locations lastObject];
    self.userLocation = newLocation;

   //tell the centralManager that you want to deferred this updatedLocation
    if (_isBackgroundMode && !_deferringUpdates)
    {
        _deferringUpdates = YES;
        [self.locationManager allowDeferredLocationUpdatesUntilTraveled:CLLocationDistanceMax timeout:10];
    }
}

4. But you should handle the data in then "locationManager:didFinishDeferredUpdatesWithError:" callback for your purpose

- (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error {

     _deferringUpdates = NO;

     //do something 
}

5. NOTE: I think we should reset parameters of LocationManager each time app switches between background/forground mode.

Harish Pathak
  • 143
  • 1
  • 4
samthui7
  • 923
  • 2
  • 12
  • 11
  • This works great and I do feel this is the right way to do as mentioned in the WWDC last year!! Thanks for the post – prasad1250 Nov 25 '14 at 04:11
  • @samthui7 thanks this works, but i need to get location after termination also. how can it be.?? – Mohit Tomar Jan 14 '16 at 11:54
  • @samthui7 .. please see this que... http://stackoverflow.com/questions/37338466/sending-current-location-to-server-in-background-as-well-as-in-running-app-using – Suraj Sukale May 21 '16 at 06:35
  • @Mohittomar what do you mean "after termination"? Is that double click Home button then swipe out the running app? If so, I don't think we can keep any background services for your app, as far as I know. – samthui7 May 22 '16 at 05:44
  • Thanks @SurajSukale, I'll take a look. – samthui7 May 22 '16 at 05:44
  • @SurajSukale I've read it but not gave it a try yet. I'll update asap. – samthui7 May 23 '16 at 09:22
  • 2
    @amar: I did check on iOS 10 beta also, it still works. Note that we need to add some code before startUpdatingLocation:`allowsBackgroundLocationUpdates = YES` and `requestAlwaysAuthorization` or `requestWhenInUseAuthorization`. E.g:` CLLocationManager *locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.allowsBackgroundLocationUpdates = YES; [locationManager requestAlwaysAuthorization]; [locationManager startUpdatingLocation];` – samthui7 Aug 20 '16 at 03:11
  • @samthui7 Don't you burn the battery with _locationManager.desiredAccuracy = 45; all the time ? At this level I think it's using GPS ("nearest ten meters" usually use GPS) – CedricSoubrie Sep 13 '16 at 09:02
  • @CedricSoubrie Obviously, the more accuracy you want, the more battery's consumed. – samthui7 Sep 13 '16 at 09:18
  • Ok but with this accuracy continuously you must really drain battery. Something like 30-40% in 12 hours (http://evgenii.com/blog/power-consumption-of-location-services-in-ios/) Or is defered update really magical ? – CedricSoubrie Sep 13 '16 at 09:23
  • @CedricSoubrie Yes, it takes quite much power as that page says. But plz notice that: Deferred update works only when `app is in the background and the system is able to optimize its power usage` (low power state) - Apple doc. When I test my above code, app running in background takes more charge than on foreground. So I guess the deferred update not solves the power consuming problem. It just meets the 2 requirements of this thread: keep updating userLoc in background, and post UserLoc changes periodically. – samthui7 Sep 13 '16 at 10:11
  • @samthui7 Will your code work even if the app is killed or terminated by the user ? – Nirav Oct 27 '16 at 12:23
  • 1
    @Nirav No. I don't think Apple could let we do that, as far as I know. – samthui7 Oct 28 '16 at 01:45
  • @samthui7 No we can do that, search an app called "zenly", it can track your friends location even if your friends have not opened "zenly" app – Nirav Oct 28 '16 at 05:06
  • @samthui7, in the latest iOS version, the deferred location method is deprecated. What is the alternate option for us for the same?? – Jayesh Kalkani Apr 18 '20 at 05:34
39
  1. If you have the UIBackgroundModes in your plist with location key then you don't need to use beginBackgroundTaskWithExpirationHandler method. That's redundant. Also you're using it incorrectly (see here) but that's moot since your plist is set.

  2. With UIBackgroundModes location in the plist the app will continue to run in the background indefinitely only as long as CLLocationManger is running. If you call stopUpdatingLocation while in the background then the app will stop and won't start again.

    Maybe you could call beginBackgroundTaskWithExpirationHandler just before calling stopUpdatingLocation and then after calling startUpdatingLocation you could call the endBackgroundTask to keep it backgrounded while the GPS is stopped, but I've never tried that - it's just an idea.

    Another option (which I haven't tried) is to keep the location manager running while in the background but once you get an accurate location change the desiredAccuracy property to 1000m or higher to allow the GPS chip to get turned off (to save battery). Then 10 minutes later when you need another location update, change the desiredAccuracy back to 100m to turn on the GPS until you get an accurate location, repeat.

  3. When you call startUpdatingLocation on the location manager you must give it time to get a position. You should not immediately call stopUpdatingLocation. We let it run for a maximum of 10 seconds or until we get a non-cached high accuracy location.

  4. You need to filter out cached locations and check the accuracy of the location you get to make sure it meets your minimum required accuracy (see here). The first update you get may be 10 mins or 10 days old. The first accuracy you get may be 3000m.

  5. Consider using the significant location change APIs instead. Once you get the significant change notification, you could start CLLocationManager for a few seconds to get a high accuracy position. I'm not certain, I've never used the significant location change services.

Community
  • 1
  • 1
progrmr
  • 75,956
  • 16
  • 112
  • 147
  • Apple `CoreLocation` reference seems to recommend the use of `beginBackgroundTaskWithExpirationHandler` when processing location data in the background: "If an iOS app needs more time to process the location data, it can request more background execution time using the beginBackgroundTaskWithName:expirationHandler: method of the UIApplication class." - https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html – eliocs Jul 29 '15 at 07:57
  • 4
    don't forget to add _locationManager.allowsBackgroundLocationUpdates = YES; for iOS9+, otherwise app will go to sleep after 180 sec – kolyancz Jun 02 '16 at 10:34
  • @kolyancz I just tried it on iOS 10.1.1 for iPhone 6+. It took roughly ~17 minutes for it to go to sleep and trigger `locationManagerDidPauseLocationUpdates`. I verified that behavior 10 times! – mfaani Jul 20 '17 at 19:00
  • I really don't get why you're saying it's moot and then you're saying you should wrap it inside a backgroundTask... – mfaani Jul 24 '17 at 18:08
10

When it is time to start location service and stop background task, background task should be stopped with a delay (1 second should be enough). Otherwise location service wont start. Also Location Service should be left ON for a couple of seconds (e.g. 3 seconds).

There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.

let manager = APScheduledLocationManager(delegate: self)
manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)

The repository also contains an example app written in Swift 3.

sash
  • 8,423
  • 5
  • 63
  • 74
1

How about giving it a try with startMonitoringSignificantLocationChanges: API? It definitely fires with less frequency and the accuracy is reasonably good. Additionally it has lot more advantages than using other locationManager API's.

Lot more regarding this API has already been discussed on this link

Community
  • 1
  • 1
thatzprem
  • 4,697
  • 1
  • 33
  • 41
  • 1
    I've tried this approach. It violates the accuracy requirement - from the [docs](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/occ/instm/CLLocationManager/startMonitoringSignificantLocationChanges): `This interface delivers new events only when it detects changes to the device’s associated cell towers` – pcoving Sep 27 '13 at 05:28
1

You need to add the location update mode in your application by adding following key in you info.plist.

<key>UIBackgroundModes</key>
<array>
    <string>location</string>
</array>

didUpdateToLocation method will be called (even when your app is in background). You can perform any thing on the bases of this method call

aahsanali
  • 3,537
  • 23
  • 21
  • Location update mode is already in the info.plist. As I mentioned, I am getting updates until the background timeout of 180 seconds. – pcoving Sep 27 '13 at 05:33
0

After iOS 8 their are several changes in CoreLocation framework related background fetch and updations made by apple.

1) Apple introduce the AlwaysAuthorization request for fetching the location update in the application.

2) Apple introduce backgroundLocationupdates for fetching location from background in iOS.

For fetching location in background you need to enable Location Update in Capabilities of Xcode.

//
//  ViewController.swift
//  DemoStackLocation
//
//  Created by iOS Test User on 02/01/18.
//  Copyright © 2018 iOS Test User. All rights reserved.
//

import UIKit
import CoreLocation

class ViewController: UIViewController {

    var locationManager: CLLocationManager = CLLocationManager()
    override func viewDidLoad() {
        super.viewDidLoad()
        startLocationManager()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    internal func startLocationManager(){
        locationManager.requestAlwaysAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.delegate = self
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.startUpdatingLocation()
    }

}

extension ViewController : CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
        let location = locations[0] as CLLocation
        print(location.coordinate.latitude) // prints user's latitude
        print(location.coordinate.longitude) //will print user's longitude
        print(location.speed) //will print user's speed
    }

}
Sunil M.
  • 554
  • 5
  • 17
0

To use standard location services while the application is in the background you need first turn on Background Modes in the Capabilities tab of the Target settings, and select Location updates.

Or, add it directly to the Info.plist.

<key>NSLocationAlwaysUsageDescription</key>
 <string>I want to get your location Information in background</string>
<key>UIBackgroundModes</key>
 <array><string>location</string> </array>

Then you need to setup the CLLocationManager

Objective C

//The Location Manager must have a strong reference to it.
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
//Request Always authorization (iOS8+)
if ([_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) { [_locationManager requestAlwaysAuthorization];
}
//Allow location updates in the background (iOS9+)
if ([_locationManager respondsToSelector:@selector(allowsBackgroundLocationUpdates)]) { _locationManager.allowsBackgroundLocationUpdates = YES;
}
[_locationManager startUpdatingLocation];

Swift

self.locationManager.delegate = self
if #available (iOS 8.0,*) {
    self.locationManager.requestAlwaysAuthorization()
}
if #available (iOS 9.0,*) {
    self.locationManager.allowsBackgroundLocationUpdates = true
}
self.locationManager.startUpdatingLocation()

Ref:https://medium.com/@javedmultani16/location-service-in-the-background-ios-942c89cd99ba

Mr.Javed Multani
  • 12,549
  • 4
  • 53
  • 52
-3
locationManager.allowsBackgroundLocationUpdates = true
Papon Smc
  • 576
  • 4
  • 11