¿Cómo obtengo una actualización de ubicación en segundo plano cada n minutos en mi aplicación iOS?

Estoy buscando una forma de obtener una actualización de ubicación de fondo cada n minutos en mi aplicación de iOS. Estoy usando iOS 4.3 y la solución debería funcionar para iPhones sin jailbreak.

Probé / consideré las siguientes opciones:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges : funciona en segundo plano como se esperaba, en función de las propiedades configuradas, pero no parece posible obligarlo a actualizar la ubicación cada n minutos
  • NSTimer : funciona cuando la aplicación se ejecuta en primer plano, pero no parece estar diseñada para tareas en segundo plano
  • Notificaciones locales: las notificaciones locales se pueden progtwigr cada n minutos, pero no es posible ejecutar algún código para obtener la ubicación actual (sin que el usuario tenga que iniciar la aplicación a través de la notificación). Este enfoque tampoco parece ser un enfoque limpio ya que no es para lo que deberían usarse las notificaciones.
  • UIApplication:beginBackgroundTaskWithExpirationHandler : Por lo que yo entiendo, esto se debe utilizar para terminar un trabajo en segundo plano (también limitado en el tiempo) cuando una aplicación se mueve al fondo en lugar de implementar procesos en segundo plano de fondo.

¿Cómo puedo implementar estas actualizaciones de ubicación de fondo regulares?

Encontré una solución para implementar esto con la ayuda de los foros de desarrolladores de Apple:

  • Especificar el location background mode
  • Cree un NSTimer en segundo plano con UIApplication:beginBackgroundTaskWithExpirationHandler:
  • Cuando n es más pequeño que UIApplication:backgroundTimeRemaining funcionará muy bien. Cuando n es más grande , el location manager la location manager debe habilitarse (y deshabilitarse) nuevamente antes de que no quede tiempo para evitar que se mate la tarea de fondo.

Esto funciona porque la ubicación es uno de los tres tipos permitidos de ejecución de fondo .

Nota: Perdí algo de tiempo al probar esto en el simulador donde no funciona. Sin embargo, funciona bien en mi teléfono.

En iOS 8/9/10 para actualizar la ubicación de fondo cada 5 minutos, haga lo siguiente:

  1. Vaya a Proyecto -> Capacidades -> Modos de fondo -> seleccione Actualizaciones de ubicación

  2. Vaya a Proyecto -> Información -> agregue una clave NSLocationAlwaysUsageDescription con valor vacío (u opcionalmente cualquier texto)

  3. Para que la ubicación funcione cuando la aplicación esté en segundo plano y envíe las coordenadas al servicio web o haga algo con ellas cada 5 minutos, impleméntela como en el siguiente código.

No estoy usando ninguna tarea de fondo o temporizadores. Probé este código con mi dispositivo con iOS 8.1, que estaba en mi escritorio durante algunas horas con mi aplicación ejecutándose en segundo plano. El dispositivo estaba bloqueado y el código se ejecutaba correctamente todo el tiempo.

 @interface LocationManager ()  @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 

Hice esto en una aplicación que estoy desarrollando. Los temporizadores no funcionan cuando la aplicación está en segundo plano, pero la aplicación recibe constantemente las actualizaciones de ubicación. Leí en algún lugar de la documentación (parece que no puedo encontrarlo ahora, publicaré una actualización cuando lo hago) que un método puede invocarse solo en un ciclo de ejecución activo cuando la aplicación está en segundo plano. El delegado de la aplicación tiene un bucle de ejecución activo incluso en el bg, por lo que no es necesario que crees el tuyo para que esto funcione. [No estoy seguro de si esta es la explicación correcta, pero así es como entendí por lo que leí]

En primer lugar, agregue el objeto de location para la clave UIBackgroundModes en el UIBackgroundModes de su aplicación. Ahora, lo que debe hacer es iniciar las actualizaciones de ubicación en cualquier lugar de su aplicación:

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

A continuación, escriba un método para manejar las actualizaciones de ubicación, por ejemplo -(void)didUpdateToLocation:(CLLocation*)location , en el delegado de la aplicación. A continuación, implemente el método locationManager:didUpdateLocation:fromLocation de CLLocationManagerDelegate en la clase en la que inició el administrador de ubicaciones (ya que configuramos el delegado del administrador de ubicaciones en ‘self’). Dentro de este método, debe verificar si ha transcurrido el intervalo de tiempo después del cual debe manejar las actualizaciones de ubicación. Puede hacer esto guardando la hora actual cada vez. Si ese tiempo ha transcurrido, llame al método UpdateLocation del delegado de su aplicación:

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

Esto llamará a su método cada 5 minutos, incluso cuando su aplicación esté en segundo plano. Imp: esta implementación agota la batería; si la precisión de los datos de su ubicación no es crítica, debe usar [locationManager startMonitoringSignificantLocationChanges]

Antes de agregar esto a su aplicación, lea la Guía de progtwigción de concientización de ubicación

Ahora que iOS6 ya salió, la mejor manera de tener una ubicación en funcionamiento permanente es …

 - (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]); } 

Acabo de probarlo así:

Inicié la aplicación, pasé al segundo plano y me mudé al automóvil unos minutos. Luego me voy a casa durante 1 hora y empiezo a moverme de nuevo (sin volver a abrir la aplicación). Las ubicaciones comenzaron de nuevo. Luego se detuvo durante dos horas y comenzó de nuevo. Todo bien de nuevo …

NO OLVIDAR EL USO DE LOS NUEVOS SERVICIOS DE LOCALIZACIÓN EN iOS6

 - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { CLLocation *loc = [locations lastObject]; // Lat/Lon float latitudeMe = loc.coordinate.latitude; float longitudeMe = loc.coordinate.longitude; } 

Para alguien que tiene pesadilla, descifrar esto. Tengo una solución simple.

  1. Mire este ejemplo de raywenderlich.com -> tenga código de muestra, esto funciona perfectamente, pero desafortunadamente no hay temporizador durante la ubicación de fondo. esto se ejecutará indefinidamente.
  2. Agregar temporizador usando:

     -(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. Simplemente no olvide agregar “registros de aplicación para actualizaciones de ubicación” en info.plist.

Esto es lo que uso:

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

Comienzo el seguimiento en AppDelegate así:

 BackgroundLocationManager.instance.start() 

Desafortunadamente, todas sus suposiciones parecen correctas, y no creo que haya una manera de hacer esto. Para ahorrar batería, los servicios de ubicación del iPhone se basan en el movimiento. Si el teléfono se encuentra en un lugar, es invisible para los servicios de ubicación.

El CLLocationManager solo llamará a locationManager:didUpdateToLocation:fromLocation: cuando el teléfono reciba una actualización de ubicación, lo cual solo ocurre si uno de los tres servicios de ubicación (cell tower, gps, wifi) percibe un cambio.

Algunas otras cosas que pueden ayudar a informar otras soluciones:

  • Al iniciar y detener los servicios, se llama al método de delegado didUpdateToLocation , pero newLocation posible que newLocation tenga una marca de tiempo anterior.

  • El monitoreo de la región podría ayudar

  • Cuando se ejecuta en segundo plano, tenga en cuenta que puede ser difícil obtener soporte de LocationServices “completo” aprobado por Apple. Por lo que he visto, han diseñado específicamente startMonitoringSignificantLocationChanges como una alternativa de baja potencia para aplicaciones que necesitan soporte de ubicación en segundo plano, y recomiendan encarecidamente a los desarrolladores que lo usen a menos que la aplicación lo necesite.

¡Buena suerte!

ACTUALIZACIÓN: estos pensamientos pueden estar desactualizados por ahora. Parece que las personas están teniendo éxito con la respuesta de @wjans, arriba.

Escribí una aplicación usando los servicios de ubicación, la aplicación debe enviar la ubicación cada 10 segundos. Y funcionó muy bien.

Simplemente use el método ” allowDeferredLocationUpdatesUntilTraveled: timeout “, siguiendo el documento de Apple.

Lo que hice fue:

Requerido: registre el modo de fondo para actualizar la ubicación.

1. Cree LocationManger y startUpdatingLocation , con accuracy y startUpdatingLocation filteredDistance como desee:

 -(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. Para que la aplicación se ejecute para siempre utilizando el método allowDeferredLocationUpdatesUntilTraveled:timeout en segundo plano, debe reiniciar la updatingLocation allowDeferredLocationUpdatesUntilTraveled:timeout con un nuevo parámetro cuando la aplicación se mueva a un segundo plano, como este:

 - (void)applicationWillResignActive:(UIApplication *)application { _isBackgroundMode = YES; [_locationManager stopUpdatingLocation]; [_locationManager setDesiredAccuracy:kCLLocationAccuracyBest]; [_locationManager setDistanceFilter:kCLDistanceFilterNone]; _locationManager.pausesLocationUpdatesAutomatically = NO; _locationManager.activityType = CLActivityTypeAutomotiveNavigation; [_locationManager startUpdatingLocation]; } 

3. La aplicación se actualiza de manera normal con 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. Pero debe manejar los datos en entonces locationManager:didFinishDeferredUpdatesWithError: callback para su propósito

 - (void) locationManager:(CLLocationManager *)manager didFinishDeferredUpdatesWithError:(NSError *)error { _deferringUpdates = NO; //do something } 

5. NOTA: Creo que debemos restablecer los parámetros de LocationManager cada vez que la aplicación cambie entre el modo de fondo / falso.

 if ([self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { [self.locationManager setAllowsBackgroundLocationUpdates:YES]; } 

Esto es necesario para el seguimiento de ubicación en segundo plano desde iOS 9.

Usé el método de xs2bush para obtener un intervalo (usando timeIntervalSinceDate ) y lo timeIntervalSinceDate un poco. Quería asegurarme de que estaba obteniendo la precisión requerida que necesitaba y también de que no estaba agotando la batería manteniendo encendida la radio gps más de lo necesario.

Mantengo la ubicación funcionando continuamente con la siguiente configuración:

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

este es un drenaje relativamente bajo de la batería. Cuando esté listo para leer mi próxima ubicación periódica, primero verifico si la ubicación está dentro de mi precisión deseada, si es así, luego uso la ubicación. Si no es así, entonces aumento la precisión con esto:

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

obtener mi ubicación y luego, una vez que tenga la ubicación, vuelvo a bajar la precisión para minimizar el consumo de la batería. Escribí una muestra completa de esto y también escribí la fuente del código del lado del servidor para recostackr los datos de ubicación, almacenarlos en una base de datos y permitirles a los usuarios ver datos gps en tiempo real o recuperar y ver rutas previamente almacenadas. Tengo clientes para iOS, Android, Windows Phone y java me. Todos los clientes se escriben de forma nativa y todos funcionan correctamente en segundo plano. El proyecto tiene licencia de MIT.

El proyecto de iOS está destinado para iOS 6 utilizando un SDK base de iOS 7. Puede obtener el código aquí .

Por favor, presente un problema en github si ve algún problema con él. Gracias.

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

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.

Working Code(Entire Stepwise Code)

Paso 1

  • Go to project -> Capabilities -> Background Modes -> select Location updates.
  • Go to Project -> Info -> add a key NSLocationAlwaysUsageDescription with an optional string.

Paso 2

Add this code to AppDelegate.m

 @interface AppDelegate () @property (strong, nonatomic) CLLocationManager *locationManager; @property (strong, nonatomic) NSTimer *timer; @end 

Step 3 Add This Code in to applicationDidEnterBackground method in AppDelegate.m

  - (void)applicationDidEnterBackground:(UIApplication *)application { UIApplication *app = [UIApplication sharedApplication]; __block UIBackgroundTaskIdentifier bgTaskId = [app beginBackgroundTaskWithExpirationHandler:^{ [app endBackgroundTask:bgTaskId]; bgTaskId = UIBackgroundTaskInvalid; }]; dispatch_async( dispatch_get_main_queue(), ^{ self.timer = nil; [self initTimer]; [app endBackgroundTask:bgTaskId]; bgTaskId = UIBackgroundTaskInvalid; }); } - (void)initTimer { if (nil == self.locationManager) self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; [self.locationManager requestAlwaysAuthorization]; [self.locationManager startMonitoringSignificantLocationChanges]; if (self.timer == nil) { self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(checkUpdates:) userInfo:nil repeats:YES]; } } - (void)checkUpdates:(NSTimer *)timer{ UIApplication *app = [UIApplication sharedApplication]; double remaining = app.backgroundTimeRemaining; if(remaining < 580.0) { [self.locationManager startUpdatingLocation]; [self.locationManager stopUpdatingLocation]; [self.locationManager startMonitoringSignificantLocationChanges]; } } - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { NSLog(@"Did Update Location = %f / %f", [newLocation coordinate].latitude, [newLocation coordinate].longitude); [self updateLocationWithLatitude:[newLocation coordinate].latitude andLongitude:[newLocation coordinate].longitude]; UIApplication* app = [UIApplication sharedApplication]; __block UIBackgroundTaskIdentifier bgTask = bgTask = [app beginBackgroundTaskWithExpirationHandler:^{ [app endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self initTimer]; }); } - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { [self.locationManager stopUpdatingLocation]; UIApplication *app = [UIApplication sharedApplication]; __block UIBackgroundTaskIdentifier bgTask = bgTask = [app beginBackgroundTaskWithExpirationHandler:^{ [app endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; [self initTimer]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Do the work associated with the task }); } -(void)updateLocationWithLatitude:(CLLocationDegrees)latitude andLongitude:(CLLocationDegrees)longitude{ //Here you can update your web service or back end with new latitude and longitude } 

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

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