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

14

212

I'm looking for a way to get a background location update every n minutes in my iOS application. I'm using iOS 4.3 and the solution should work for non-jailbroken iPhones.

I tried / considered following options:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: This works in the background as expected, based on the configured properties, but it seems not possible to force it to update the location every n minutes
  • NSTimer: Does work when the app is running in the foreground but doesn't seem to be designed for background tasks
  • Local notifications: Local notifications can be scheduled every n minutes, but it's not possible to execute some code to get the current location (without the user having to launch the app via the notification). This approach also doesn't seem to be a clean approach as this is not what notifications should be used for.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: As far as I understand, this should be used to finish some work in the background (also limited in time) when an app is moved to the background rather than implementing "long-running" background processes.

How can I implement these regular background location updates?

Fornicate answered 14/6, 2011 at 17:23 Comment(4)
possible duplicate of to run app continously in the backgroundChinquapin
useful follow-up: #10235703Calchas
If you are trying to make it work on iOS 7, you may try this solution here: #18947381 If you have any question, you are welcomed to join us for a discussion here: mobileoop.com/background-location-update-programming-for-ios-7Darwindarwinian
All your findings are correct (the four bullet points). Valuable information that is then, knowing what does not match you use case? And yes, when in SUSPENDED MODE or NOT RUNNING, there is no ultimate method for updating every n-minutes.Triturable
F
117

I found a solution to implement this with the help of the Apple Developer Forums:

  • Specify location background mode
  • Create an NSTimer in the background with UIApplication:beginBackgroundTaskWithExpirationHandler:
  • When n is smaller than UIApplication:backgroundTimeRemaining it will work just fine. When n is larger, the location manager should be enabled (and disabled) again before there is no time remaining to avoid the background task being killed.

This works because location is one of the three allowed types of background execution.

Note: I lost some time by testing this in the simulator where it doesn't work. However, it works fine on my phone.

Fornicate answered 24/6, 2011 at 8:32 Comment(39)
do you happen to have the link to the forum. I'm looking to implement the same type of location mapping and haven't been able to get it to work. Or some sample code would be greatly appreciated.Bullring
Can you please explain why the background task is not killed after 10 mins (maximum allowed time) if you just stop and start the location manager? is it some sort of intended functionality? it sounds more like a bug in Apple SDK if it happens like that. Which iOS version you were trying it on?Assail
@cwieland: The forum post doesn't mention any more information but all individual "steps" are clearly explained in the Apple docs. In case you run into specific problems, I suggest you post your own question.Fornicate
@saurabh: As per my understanding, it works as designed since location updates is a valid background processing mode. It does work on different iOS versions (4.X, 5.X), don't think it's a bug.Fornicate
If n is larger than 10 mins(which is the extra time the iOs gives you) you will end up sending the coordinates after that 10 mins, as you said, "in case n is larger, the location manager should be enabled (and disabled) " wich produces "didUpdateLocation" to execute. Im sure it works for you but could you post some code so I can get a better picture of what you did?, this EXACTLY what Im looking for.Bordelon
@DavidShaikh: I literally implemented it like this: [self.locationManager startUpdatingLocation]; [self.locationManager stopUpdatingLocation];. In didUpdateToLocation I just check whether or not I'm interested in the update.Fornicate
@Fornicate I'm not getting your solutions ... If I get you right you are running NSTimer in beginBackgroundTaskWithExpirationHandler scheduling to startUpdateLocation every n minutes right ? But what you do if n is larger than backgroundTimeRemaining ? Where do you enable and disable locationManager ? Shall the NSTimer work at all if n > backgroundTimeRemaining ???Concentric
@deimus: No, that's right. In case n is larger, scheduling a timer every n minuties won't work. Therefor you have to make sure to enable it sooner, before backgroundTimeRemaining becomes zero.Fornicate
@Fornicate I'm really cluttered in your solution ... just couple of my questions 1. Has your code ever worked for you if n is larger than backgroundTimeRemaining ? 2. How you have used NSTimer with UIApplication:beginBackgroundTaskWithExpirationHandler: particularely i'm insterested how you have scheduled NSTimer ?Concentric
@deimus: 1. Yes, by enabling and immediately disabling the location mgr as described. 2. When using UIApplication:beginBackgroundTaskWithExpirationHandler, you specificy a block of code to be executed. In that block, you can simply start a timer as described in the Apple documentation.Fornicate
@Fornicate I'm really interested only in case when n is larger than backgroundTimeRemaining... I guess NSTimer will not work work... you suggested to enable it sooner ... actually no one knows when user will send application to background ... what do you mean under "sooner" ?Concentric
@Fornicate let say n is 14 mins and backgroundTimeRemaining is 10 mins ... Can you please describe the workflow of you code. When NSTimer will fire the scheduled method, starting from the point when application goes to background.Concentric
Finally after more than hour of thinking got the code working... I simply couldn't understand why the background task was not killed after 10 mins (backgroundTimeRemaining) ... as also @Assail mentioned in his comment ... So my last question to you, have your app passed Apple submission, this is not the violation of app acceptance guideline ?Concentric
@Fornicate can anybody provide me the sample code for that Background process work flow at a time. i get location at particular time. not at a didupdatelocation method. i really need to work out as the asked questionMesocarp
I wonder if anybody could post the code. also as demus asked. did this pass Apple submission .Bacardi
@all: Yes, our app is available in the AppStore. I am not gonna post all the code, all mentioned bulletpoints are clearly documented features. In case you are having a specific problem, post your own question explaining what you have tried and what goes wrong.Fornicate
Yes,I tested it & it works as you described. Basically if you started " [locationManager startUpdatingLocation];" in "beginBackgroundTaskWithExpirationHandler" it works forever, it doesn't stop as excepted. I'm just worried that this could be a bug and Apple might fix it in future.Bacardi
@user836026: If you don't stop the location manager, your app won't be terminated. That works as designed. In my scenario, I stop the location manager.Fornicate
@Fornicate do you mean by "Specify location background mode" to set UIBackgroundModes=location in Info.plist file?Bacardi
Works nice, however I don't like very much that the user is prompted to allow user location. Any way thanks :).Gyimah
So correct me if i'm wrong. for this to work, you need to start/stop location update "every 10 minutes".Bacardi
@user836026: Yes, that's what I mean by specifying background mode. After stopping the location update, it should be started again within 10 minutes to avoid the app from being terminated.Fornicate
@wjans, it works fine now with me ... just for fine tuning, I only turn on location update for short period of time - only 2 seconds - to save battery. do you think 2 seconds is OK, or too short to get high accuracy location update.Bacardi
do you guys have any test of the battery consumption of this method?Lumbye
this compared to "always On" location update is much better, i didn't notice battery leaking. However, I didn't do through testing.Bacardi
on my test there is not such a difference. I've done a test comparing: my code with this method, mylocus (on appStore) and the "always On" method.Lumbye
@luka: do you know how mylocus app works? does it use background location update or significant change only?Bacardi
it think (I haven't seen the implementation of course) it uses background location update + NSTimerLumbye
Did you test mylocus app? does it drain battery, or there is no noticable effect on battery.Bacardi
@Fornicate hey .. .can you post your code somewhere as if I am also looking for similar things.ThanksAutomate
For all those interested in seeing some actual code reflecting what is being discussed here, check #10235703Calchas
there is some problem with this approach on iOS 7, take a look at #18902083Murdocca
Anyone figured out how to use a larger interval than the background task?Neville
@All which solution this or below samthui7's explained solution should I prefer to save the device battery or both will affect the same? And can these both solutions will work on iOS 7 and iOS 8?Halpern
Here's a great location framework for iOS that does it all for you: github.com/IngeoSDK/ingeo-ios-sdkMultiversity
Can anyone share the code regarding this solution? Is that the timer start, and every time it trigger and check the value of n to toggle the state of the LocationManager? If it is the case, how can we avoid the timer being killed when the app is at the background?Siouxie
@wjans. WhenInUse service user moves from online to offline will it return correct coordinates ? till what time? if user offline is it good idea to call "StartUpdatingLocation" if user moving?Keddah
@Fornicate How do you remove the status bar location icon during the "cooldown" (betweeen the stopUpdatingLocation to the next startUpdatingLocation)? It stays there even after the calling to stopUpdatingLocation. The icon is only get disappeared after explicitly calling stopUpdatingLocation (from a ViewController or something like that)Ealdorman
Is this answer still valid on iOS 13? I'm trying your solution but cant get it to works.Deplume
K
56

On iOS 8/9/10 to make background location update every 5 minutes do the following:

  1. Go to Project -> Capabilities -> Background Modes -> select Location updates

  2. Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with empty value (or optionally any text)

  3. To make location working when your app is in the background and send coordinates to web service or do anything with them every 5 minutes implement it like in the code below.

I'm not using any background tasks or timers. I've tested this code with my device with iOS 8.1 which was lying on my desk for few hours with my app running in the background. Device was locked and the code was running properly all the time.

@interface LocationManager () <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSDate *lastTimestamp;

@end

@implementation LocationManager

+ (instancetype)sharedInstance
{
    static id sharedInstance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        LocationManager *instance = sharedInstance;
        instance.locationManager = [CLLocationManager new];
        instance.locationManager.delegate = instance;
        instance.locationManager.desiredAccuracy = kCLLocationAccuracyBest; // you can use kCLLocationAccuracyHundredMeters to get better battery life
        instance.locationManager.pausesLocationUpdatesAutomatically = NO; // this is important
    });

    return sharedInstance;
}

- (void)startUpdatingLocation
{
    CLAuthorizationStatus status = [CLLocationManager authorizationStatus];

    if (status == kCLAuthorizationStatusDenied)
    {
        NSLog(@"Location services are disabled in settings.");
    }
    else
    {
        // for iOS 8
        if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        {
            [self.locationManager requestAlwaysAuthorization];
        }
        // for iOS 9
        if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)])
        {
            [self.locationManager setAllowsBackgroundLocationUpdates:YES];
        }

        [self.locationManager startUpdatingLocation];
    }
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *mostRecentLocation = locations.lastObject;
    NSLog(@"Current location: %@ %@", @(mostRecentLocation.coordinate.latitude), @(mostRecentLocation.coordinate.longitude));

    NSDate *now = [NSDate date];
    NSTimeInterval interval = self.lastTimestamp ? [now timeIntervalSinceDate:self.lastTimestamp] : 0;

    if (!self.lastTimestamp || interval >= 5 * 60)
    {
        self.lastTimestamp = now;
        NSLog(@"Sending current location to web service.");
    }
}

@end
Koa answered 6/2, 2015 at 19:24 Comment(17)
Does this work for more than a few hours? It seems like even if the app is not terminated, the device stops pushing location updates after an hour or so of the app going to the background.Caryncaryo
I got it working for 20 hours in background and did not notice any problems. Are you sure you did set everything like above?Koa
Interesting. I have pausesLocationUpdatesAutomatically set to YES, but I'm guessing it should resume once I start moving again, which it does not seem to be doing. Do you have Background Fetch checked too, under Capabilities -> Background Modes?Caryncaryo
Background Fetch is disabled in my project (it should not matter if it is off or on). However pausesLocationUpdatesAutomatically must be set to NO for above example to work properly. It will NOT resume automatically once you start moving again if it was paused by the system previously. That is why in above example I set this property to NO.Koa
I think you are absolutely right. Found some more information about the issue here: https://mcmap.net/q/128723/-iphone-gps-in-background-never-resumes-after-pause/1048331Caryncaryo
Hi @LeszekS, I'm learning about background process and am also looking for similar example. Your solution is very close to what I'm looking for. I wonder how can I attach the LocationManager to a UIViewController or AppDelegate? I have no slightest idea how to execute the codes in your example. Can you please provide some ideas how to execute your codes? Thanks,Chryselephantine
@AdrianHoe in your view controller just call [[LocationManager sharedInstance] startUpdatingLocation]; and location update will start working.Koa
Any specific reason why this should not work in iOS 7?Dovev
Did not test it on iOS 7 but probably it should also work fine.Koa
Of course the code above is only the content of .m file. I assumed it will be obvoius that you also need to create an interface declaration in .h file to make it work. Also you must add declarations of sharedInstance and startUpdatingLocation methods in your interface (.h) file to be able to call it from AppDelegate.Koa
Is this the same for iOS9? Also, Is it possible to get location every 5 minutes while the app is terminated? Say, a service that starts automatically when the device is started?Stav
@Stav that's not possible.Perverted
@LeszekS you need to add below code for iOS 9 support for background - if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; }Unstuck
@LeszekS from where are you taking lastTimestamp ?Symmetrize
What's the convenience of doing this? You're still draining the battery by having a constant high accuracy. The only thing you don't do is you don't actually use the already received location until you reach an interval of 5 minutes...Pettis
@Honey yes you are absolutely right about battery life, but still I can see reasons why someone would need only 1 location reading per x minutes and that was the question asked here so above code is the answer to this question. I also mentioned in the comment above that kCLLocationAccuracyHundredMeters can be used for better battery life.Koa
@LeszekS : please let me know how to get current location even though app is killed or terminated , i have tried using significantlocation but didn't work?Bobsled
H
34

I did this in an application I'm developing. The timers don't work when the app is in the background but the app is constantly receiving the location updates. I read somewhere in the documentation (i can't seem to find it now, i'll post an update when i do) that a method can be called only on an active run loop when the app is in the background. The app delegate has an active run loop even in the bg so you dont need to create your own to make this work. [Im not sure if this is the correct explanation but thats how I understood from what i read]

First of all, add the location object for the key UIBackgroundModes in your app's info.plist. Now, what you need to do is start the location updates anywhere in your app:

    CLLocationManager locationManager = [[CLLocationManager alloc] init];
    locationManager.delegate = self;//or whatever class you have for managing location
    [locationManager startUpdatingLocation];

Next, write a method to handle the location updates, say -(void)didUpdateToLocation:(CLLocation*)location, in the app delegate. Then implement the method locationManager:didUpdateLocation:fromLocation of CLLocationManagerDelegate in the class in which you started the location manager (since we set the location manager delegate to 'self'). Inside this method you need to check if the time interval after which you have to handle the location updates has elapsed. You can do this by saving the current time every time. If that time has elapsed, call the method UpdateLocation from your app delegate:

NSDate *newLocationTimestamp = newLocation.timestamp;
NSDate *lastLocationUpdateTiemstamp;

int locationUpdateInterval = 300;//5 mins

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (userDefaults) {

        lastLocationUpdateTiemstamp = [userDefaults objectForKey:kLastLocationUpdateTimestamp];

        if (!([newLocationTimestamp timeIntervalSinceDate:lastLocationUpdateTiemstamp] < locationUpdateInterval)) {
            //NSLog(@"New Location: %@", newLocation);
            [(AppDelegate*)[UIApplication sharedApplication].delegate didUpdateToLocation:newLocation];
            [userDefaults setObject:newLocationTimestamp forKey:kLastLocationUpdateTimestamp];
        }
    }
}

This will call your method every 5 mins even when your app is in background. Imp: This implementation drains the battery, if your location data's accuracy is not critical you should use [locationManager startMonitoringSignificantLocationChanges]

Before adding this to your app, please read the Location Awareness Programming Guide

Hallway answered 24/6, 2011 at 9:50 Comment(4)
That way the location services are constantly enabled (indeed battery draining), I don't want that. I want to enable the location service every n minutes and immediately disable it when I have a good fix (just noticed that I didn't explain that clearly in my question). I can achieve this behaviour in the solution I described.Fornicate
You can set location manager accuracy to 1km - this will leave your battery almost intact. After 5min you set accuracy to 1m. When you get satisfying location (normally after 5s) just set accuracy back to 1km.Ila
knagode, I tried your suggested solution for battery draining issue, but even after increase accuracy after N minutes, locationManager: didUpdateLocations method is not called again. I tried startUpdating and stopUpdating, instead of increase and decrease accuracy, it is called successfully delegate locationManager: didUpdateLocations method, after N minutes, but is not working in Background MODE...Candlenut
As for the links of documentation. See here: "The methods of your delegate object are called from the thread in which you started the corresponding location services. That thread must itself have an active run loop, like the one found in your application’s main thread."Pettis
M
24

Now that iOS6 is out the best way to have a forever running location services is...

- (void)applicationWillResignActive:(UIApplication *)application
{
/*
 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.
 */

NSLog(@"to background");

app.isInBackground = TRUE;

UIApplication *app = [UIApplication sharedApplication];

// Request permission to run in the background. Provide an
// expiration handler in case the task runs long.
NSAssert(bgTask == UIBackgroundTaskInvalid, nil);

bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
    // Synchronize the cleanup call on the main thread in case
    // the task actually finishes at around the same time.
    dispatch_async(dispatch_get_main_queue(), ^{

        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task.

    locationManager.distanceFilter = 100;
    locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
    [locationManager startMonitoringSignificantLocationChanges];
    [locationManager startUpdatingLocation];

    NSLog(@"App staus: applicationDidEnterBackground");
    // Synchronize the cleanup call on the main thread in case
    // the expiration handler is fired at the same time.
    dispatch_async(dispatch_get_main_queue(), ^{
        if (bgTask != UIBackgroundTaskInvalid)
        {
            [app endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        }
    });
});

NSLog(@"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);

}

Just tested it like that:

I started the app, go background and move in the car by some minutes. Then I go home for 1 hour and start moving again (without opening again the app). Locations started again. Then stopped for two hours and started again. Everything ok again...

DO NOT FORGET USING the new location services in iOS6

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{   
    CLLocation *loc = [locations lastObject];

    // Lat/Lon
    float latitudeMe = loc.coordinate.latitude;
    float longitudeMe = loc.coordinate.longitude;
}
Morissa answered 24/9, 2012 at 11:13 Comment(13)
If the app crashes or is killed, the system doesn't restart, right?Malt
For more readable code, you can do a [locations lastObject]; instead of the [locations objectAtIndex:[locations count] - 1]Gap
your method is only at ios6?Undercut
Do we have to use this? I thought just have the location manager code in viewDidLoad of a class, and have set a background key in the plist file that the app registers for Location updates should be good enough? Can you please help me with this!Guidance
Yes, it will work like charm nithinreddy but it will stop working after 10 minutes since iOS kills long threads after that period. My solution is perfect if you want to have those services started forever. I made some tests two days ago and it drains 6% battery each hour. Using [locationManager startUpdatingLocation] instead will drain 17%Morissa
When using your code, I need to move the [locationManager startUpdatingLocation]; call to be outside of the dispatch_async() block (so it ends up between the bgTask and dispatch_async()). Otherwise I won't receive any location updates (locationManager: didUpdateLocations:). Am I doing something wrong?Paulus
@Paulus have you enabled location services on background? Please check your plist file. My code should work without modification. I have used it on two appsMorissa
@AlejandroLuengo Yeah, my plist file is right. I have a class that manages the location updates. In the applicationWillResignActive: method in my AppDelegate I call a method from this class, to start the updates. When using the first startUpdatingLocation: call, everything works (forever). However, when using the second call (as in your example), location updates stop after ten minutes. Thanks!Paulus
@Paulus please note you must add this interface on the code where do receive locationManager updates. (@)interface AppDelegate : UIResponder <UIApplicationDelegate> + (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations; (@)end Also you need TWO location methods: one for regular updates and one for bg updates, like this: - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations and - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocationMorissa
@Paulus send me an email if you want more help. I will be glad helping you. You can reachme at www.atomstudios.es website, find contact. I don't want to put the email here to avoid spam spidersMorissa
@AlejandroLuengo thanks for your answer. I am doing an ios app which tracks user locations. I need to send the new location co ordinates to the server every 30 seconds and need to update the location again once I get response from the server. The problem is sometimes didUpdateToLocation is not called and in that method only I am sending request to the server. I am really helpless here. Do you have any suggestion ? If you have, let me know.Chord
didUpdateToLocation only gets called when location changes. You should send the last location received every 30 seconds since if you don't move you won't receive a new locationMorissa
I'm not sure how this is any different from enabling significant change monitoring by itself. That's supposed to wake your app up and make it monitor location changes when you move 500m+ (which sounds like what you did). The rest seems kind of meaningless to me. Am I missing something?Swellfish
B
13

To someone else having nightmare figure out this one. I have a simple solution.

  1. look this example from raywenderlich.com-> have sample code, this works perfectly, but unfortunately no timer during background location. this will run indefinitely.
  2. Add timer by using :

    -(void)applicationDidEnterBackground {
    [self.locationManager stopUpdatingLocation];
    
    UIApplication*    app = [UIApplication sharedApplication];
    
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
     self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
                                                  target:self.locationManager
                                                selector:@selector(startUpdatingLocation)
                                                userInfo:nil
                                                 repeats:YES];
    
    }
    
  3. Just don't forget to add "App registers for location updates" in info.plist.

Beckner answered 1/6, 2013 at 11:11 Comment(5)
Does this get the location for more than 3 minutes?Neville
Must set location in Capabilities -> Background Mode.Bustee
It's not working!! ?I have checked on iOS 8 ans iOS 10Normannormand
Correct me if I'm wrong. Based on what I read, you start locationManager only once. After that all intervals are redundant. Since it's already been startedPettis
it is working but it is getting stopped after 170 seconds. i want to run my task in background for unlimited timeMarniemaro
A
9

Here is what I use:

import Foundation
import CoreLocation
import UIKit

class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {

    static let instance = BackgroundLocationManager()
    static let BACKGROUND_TIMER = 150.0 // restart location manager every 150 seconds
    static let UPDATE_SERVER_INTERVAL = 60 * 60 // 1 hour - once every 1 hour send location to server

    let locationManager = CLLocationManager()
    var timer:NSTimer?
    var currentBgTaskId : UIBackgroundTaskIdentifier?
    var lastLocationDate : NSDate = NSDate()

    private override init(){
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.activityType = .Other;
        locationManager.distanceFilter = kCLDistanceFilterNone;
        if #available(iOS 9, *){
            locationManager.allowsBackgroundLocationUpdates = true
        }

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.applicationEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
    }

    func applicationEnterBackground(){
        FileLogger.log("applicationEnterBackground")
        start()
    }

    func start(){
        if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        } else {
                locationManager.requestAlwaysAuthorization()
        }
    }
    func restart (){
        timer?.invalidate()
        timer = nil
        start()
    }

    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
        switch status {
        case CLAuthorizationStatus.Restricted:
            //log("Restricted Access to location")
        case CLAuthorizationStatus.Denied:
            //log("User denied access to location")
        case CLAuthorizationStatus.NotDetermined:
            //log("Status not determined")
        default:
            //log("startUpdatintLocation")
            if #available(iOS 9, *){
                locationManager.requestLocation()
            } else {
                locationManager.startUpdatingLocation()
            }
        }
    }
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        if(timer==nil){
            // The locations array is sorted in chronologically ascending order, so the
            // last element is the most recent
            guard let location = locations.last else {return}

            beginNewBackgroundTask()
            locationManager.stopUpdatingLocation()
            let now = NSDate()
            if(isItTime(now)){
                //TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
            }
        }
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        CrashReporter.recordError(error)

        beginNewBackgroundTask()
        locationManager.stopUpdatingLocation()
    }

    func isItTime(now:NSDate) -> Bool {
        let timePast = now.timeIntervalSinceDate(lastLocationDate)
        let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
        return intervalExceeded;
    }

    func sendLocationToServer(location:CLLocation, now:NSDate){
        //TODO
    }

    func beginNewBackgroundTask(){
        var previousTaskId = currentBgTaskId;
        currentBgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
            FileLogger.log("task expired: ")
        })
        if let taskId = previousTaskId{
            UIApplication.sharedApplication().endBackgroundTask(taskId)
            previousTaskId = UIBackgroundTaskInvalid
        }

        timer = NSTimer.scheduledTimerWithTimeInterval(BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
    }
}

I start the tracking in AppDelegate like that:

BackgroundLocationManager.instance.start()
Albumin answered 6/7, 2016 at 13:36 Comment(6)
Thanks. Our project needs to track user location + send PubNub events in the background and your solution is working really well.Taste
Hi Hmitkov, Where I Can Call sendLocationToServer method to send user location on serverTasse
@AmanGupta007 You can call sendLocationToServer in func locationManager(manager: didUpdateLocations:). Notice the //TODO comment in the code.Albumin
@Albumin Is it possible to start and stop Location Services with this while app is in the background? Example, start location service from a push notification grab some lat/long, send to webservice, then stop updating location. Do this each time the 'content-available' = 1 is included in the push body.S
@S You don't need to do it this way. The purpose of the code above is to keep the app alive in the background at all times. While in your case you don't need your app to stay active in the background. When your app receives a PN with 'content-available' = 1, it is automatically activated in the background and the method application.didReceiveRemoteNotification is called. All you need to do inside that method is to start the location manager for a fresh location update, send it to your server and call the completion handler.Albumin
I've tried this code but it seems like it's not working on iOS11? (I haven't test it on any other versions.)Dust
C
6

Unfortunately, all of your assumptions seem correct, and I don't think there's a way to do this. In order to save battery life, the iPhone's location services are based on movement. If the phone sits in one spot, it's invisible to location services.

The CLLocationManager will only call locationManager:didUpdateToLocation:fromLocation: when the phone receives a location update, which only happens if one of the three location services (cell tower, gps, wifi) perceives a change.

A few other things that might help inform further solutions:

  • Starting & Stopping the services causes the didUpdateToLocation delegate method to be called, but the newLocation might have an old timestamp.

  • Region Monitoring might help

  • When running in the background, be aware that it may be difficult to get "full" LocationServices support approved by Apple. From what I've seen, they've specifically designed startMonitoringSignificantLocationChanges as a low power alternative for apps that need background location support, and strongly encourage developers to use this unless the app absolutely needs it.

Good Luck!

UPDATE: These thoughts may be out of date by now. Looks as though people are having success with @wjans answer, above.

Cherlynchernow answered 14/6, 2011 at 17:48 Comment(4)
There are apps available in the AppStore (like for instance "My Locus") that do make it possible to get a location update in the background. They don't keep the location service active but it just gets enabled shortly as per the interval defined. How do they do this?Fornicate
In the situation you describe, the app is most likely using the startMonitoringSignificantLocationChanges approach. Here, the phone gets temporarily 'woken up' when it receives a location update, but there's no interval that you can set to 'ping' this service in the background. When the phone moves (or switches from Cellular to GPS or to Wifi), it triggers an update. The Stanford iTunes U lecture on the topic was really helpful for me - hopefully it can help you find a workaround: itunes.apple.com/us/itunes-u/iphone-application-development/…Cherlynchernow
Thx for the pointer. However, I still don't understand what the app is doing though. Even if my phone is at my desk, not moving at all, I can see the location service being triggered every 10 minutes (exactly). If I understand correctly, startMonitoringSignificantLocationChanges wouldn't give any update in that case.Fornicate
@Fornicate how is your phone battery consumption, did you notice it drain quicly maybe due to Mylocus app?Bacardi
T
6

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.

What I did are:

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.

Tao answered 9/7, 2014 at 11:36 Comment(5)
@All which solution this or above wjans's explained solution should I prefer to save the device battery or both will affect the same?Halpern
I did try both solutions you mention, and saw that @wjans's one is a little bit more save battery. But, after the coming of iOS 8, it seems that that solution does not work properly anymore. For more detail: most of time, app cannot have long live in background mode.Tao
@Tao Why do you set pausesLocationUpdatesAutomatically = false ?Tirado
My app's requirement is to keep sending userLocation every 10s, while pausesLocationUpdatesAutomatically = true tells location manager to pause updates if there is seemly no location change (Apple's doc). Anyway, I did not explicitly test the case of location manager pauses updates yet :D.Tao
@CedricSoubrie: Setting pausesLocationUpdatesAutomatically = true causes my app to stop updating location.Tao
A
5
if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
    [self.locationManager setAllowsBackgroundLocationUpdates:YES];
}

This is needed for background location tracking since iOS 9.

Athanasia answered 13/1, 2016 at 6:48 Comment(1)
This saved my day! using iOS8 deployment target, with iOS9 deviceSaddletree
S
4

I used xs2bush's method of getting an interval (using timeIntervalSinceDate) and expanded on it a little bit. I wanted to make sure that I was getting the required accuracy that I needed and also that I was not running down the battery by keeping the gps radio on more than necessary.

I keep location running continuously with the following settings:

locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
locationManager.distanceFilter = 5;

this is a relatively low drain on the battery. When I'm ready to get my next periodic location reading, I first check to see if the location is within my desired accuracy, if it is, I then use the location. If it's not, then I increase the accuracy with this:

locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
locationManager.distanceFilter = 0;

get my location and then once I have the location I turn the accuracy back down again to minimize the drain on the battery. I have written a full working sample of this and also I have written the source for the server side code to collect the location data, store it to a database and allow users to view gps data in real time or retrieve and view previously stored routes. I have clients for iOS, android, windows phone and java me. All clients are natively written and they all work properly in the background. The project is MIT licensed.

The iOS project is targeted for iOS 6 using a base SDK of iOS 7. You can get the code here.

Please file an issue on github if you see any problems with it. Thanks.

Snapshot answered 4/2, 2014 at 2:45 Comment(7)
I tried your solution, but is not working... when app goes to background then, even after increase accuracy, app does not gettign called didUpdateToLocation method in my caseCandlenut
@HardikDarji important question. Are you moving? If not, location updates may stop. Try taking your phone out for a walk or a drive and see if that fixes it.Snapshot
Thanks for your quick response. But I want location updates in every 2 min, without care of my phone is moving or not. In this scenario didUpdateToLocation method is not called. I am looking here for : How do I get location update every n minutes !!!Candlenut
try setting the timeIntervalInSeconds to 120 and uncomment this line: locationManager.pausesLocationUpdatesAutomatically = NO;Snapshot
I also tried with locationManager.pausesLocationUpdatesAutomatically = NO; aslo set Location_Update_Interval = 120. I am calling reduceTrackingAccuracy method, in locationManager:didUpdateLocations delegate, and also schedule for calling increaseTrackingAccuracy method after delay of 115 seconds. increaseTrackingAccuracy is calling, but after increase accuracy, locationManager:didUpdateLocations delegate method is not getting called..Candlenut
its working nick !!! if i am not moving, but move with minimum distance filter, its getting called locationManager:didUpdateLocations method. thanks:)Candlenut
does the solution you posted in the above comment kill the battery life, or are you still doing some optimization for that?Hyderabad
C
2

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

Commodus answered 30/6, 2015 at 17:52 Comment(0)
M
2

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.

Murdocca answered 29/10, 2016 at 14:40 Comment(2)
do you have anything like this in objective c ?Bedplate
i used to use this, but it does not update while in background anymore and this is not maintained for the last 5 years or more. Here on this page to update or find a better solution.Anthropomorphosis
V
1

In iOS 9 and watchOS 2.0 there's a new method on CLLocationManager that lets you request the current location: CLLocationManager:requestLocation(). This completes immediately and then returns the location to the CLLocationManager delegate.

You can use an NSTimer to request a location every minute with this method now and don't have to work with startUpdatingLocation and stopUpdatingLocation methods.

However if you want to capture locations based on a change of X meters from the last location, just set the distanceFilter property of CLLocationManger and to X call startUpdatingLocation().

Venery answered 17/1, 2018 at 17:31 Comment(0)
K
-1

Attached is a Swift solution based in:

Define App registers for location updates in the info.plist

Keep the locationManager running all the time

Switch kCLLocationAccuracy between BestForNavigation (for 5 secs to get the location) and ThreeKilometers for the rest of the wait period to avoid battery drainage

This example updates location every 1 min in Foreground and every 15 mins in Background.

The example works fine with Xcode 6 Beta 6, running in a iOS 7 device.

In the App Delegate (mapView is an Optional pointing to the mapView Controller)

func applicationDidBecomeActive(application: UIApplication!) {
    if appLaunched! == false { // Reference to mapView used to limit one location update per timer cycle
        appLaunched = true
        var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        var window = appDelegate.window
        var tabBar = window?.rootViewController as UITabBarController
        var navCon = tabBar.viewControllers[0] as UINavigationController
        mapView = navCon.topViewController as? MapViewController
    }
    self.startInitialPeriodWithTimeInterval(60.0)
}

func applicationDidEnterBackground(application: UIApplication!) {
    self.startInitialPeriodWithTimeInterval(15 * 60.0)
}

func startInitialPeriodWithTimeInterval(timeInterval: NSTimeInterval) {
    timer?.invalidate() // reset timer
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getFirstLocationUpdate:"), userInfo: timeInterval, repeats: false)
}

func getFirstLocationUpdate(sender: NSTimer) {
    let timeInterval = sender.userInfo as Double
    timer?.invalidate()
    mapView?.canReportLocation = true
    timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("waitForTimer:"), userInfo: timeInterval, repeats: true)
}

func waitForTimer(sender: NSTimer) {
    let time = sender.userInfo as Double
    locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    finalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getLocationUpdate"), userInfo: nil, repeats: false)
}

func getLocationUpdate() {
    finalTimer?.invalidate()
    mapView?.canReportLocation = true
}

In the mapView (locationManager points to the object in the AppDelegate)

override func viewDidLoad() {
    super.viewDidLoad()
    var appDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
    locationManager = appDelegate.locationManager!
    locationManager.delegate = self
    canReportLocation = true
}

  func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
        if canReportLocation! {
            canReportLocation = false
            locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
        } else {
            //println("Ignore location update")
        }
    }
Keeton answered 25/8, 2014 at 22:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.