Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

People also ask

How often does location update on iPhone?

Significant-change location updates wake the system and your app once every 15 minutes, at minimum, even if no location changes have occurred.

Does Apple location update automatically?

Frequently Asked Questions about Find My iPhone From iOS 13, the “Find My” app automatically updates the shared location status after every minute. However, if you want, you can manually update the location status as well.

What does background location mean on iPhone?

Background location use. If you give an app permission to access your location while it's in use, the app can then ask to know where you are at all times.

What does background location use mean?

Android 10 features a background access location reminder, which increases transparency into how much access apps have to a device's location and helps users maintain control over such access. In Android 9 and lower, an app can track a device's location while running in the background without the user's knowledge.


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.


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

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


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;
}