Как получить обновление фонового местоположения каждые n минут в приложении iOS?

Я ищу способ получить обновление фонового местоположения каждые n минут в приложении iOS. Я использую iOS 4.3, и решение должно работать для не-jailbroken iPhone'ов.

Я попробовал/рассмотрел следующие варианты:

  • CLLocationManager startUpdatingLocation/startMonitoringSignificantLocationChanges: это работает в фоновом режиме, как и ожидалось, на основе настроенных свойств, но кажется невозможным заставить его обновлять местоположение каждые n минут.
  • NSTimer: работает, когда приложение работает на переднем плане, но, похоже, не предназначено для фоновых задач.
  • Локальные уведомления: локальные уведомления можно планировать каждые n минут, но невозможно выполнить какой-либо код для получения текущего местоположения (без необходимости запуска приложения через уведомление). Этот подход также, похоже, не является чистым подходом, поскольку это не то, для чего следует использовать уведомления.
  • UIApplication:beginBackgroundTaskWithExpirationHandler: Насколько я понимаю, это должно использоваться для завершения некоторой работы в фоновом режиме (также ограниченной во времени), когда приложение перемещается на задний план, а не реализует "длительные" фоновые процессы.

Как я могу реализовать эти регулярные обновления фонового местоположения?

Ответ 1

Я нашел решение для реализации этого с помощью форумов Apple Developer:

  • Укажите location background mode
  • Создайте NSTimer в фоновом режиме с помощью UIApplication:beginBackgroundTaskWithExpirationHandler:
  • Когда n меньше, чем UIApplication:backgroundTimeRemaining, он будет работать нормально. Если n больше, location manager должен быть включен (и отключен) еще раз, пока не осталось времени, чтобы избежать утери фоновой задачи.

Это работает, потому что местоположение является одним из трех разрешенных типов выполнения фона.

Примечание. Я потерял некоторое время, протестировав это в симуляторе, где он не работает. Тем не менее, он отлично работает на моем телефоне.

Ответ 2

В iOS 8/9/10, чтобы обновлять фоновые настройки каждые 5 минут, выполните следующие действия:

  • Перейти к проекту → Возможности → Режимы фона → выбрать обновления местоположения

  • Перейдите в Project → Info → добавьте ключ NSLocationAlwaysUsageDescription с пустым значением (или, возможно, любым текстом)

  • Чтобы сделать работу местоположения, когда ваше приложение находится в фоновом режиме, и отправьте координаты веб-службе или сделайте что-нибудь с ними каждые 5 минут, выполните это как в коде ниже.

Я не использую фоновые задачи или таймеры. Я тестировал этот код с помощью своего устройства с iOS 8.1, которое лежало на моем столе в течение нескольких часов, когда мое приложение работало в фоновом режиме. Устройство было заблокировано, и код работал нормально все время.

@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

Ответ 3

Я сделал это в приложении, которое я разрабатываю. Таймеры не работают, когда приложение находится в фоновом режиме, но приложение постоянно получает обновления местоположения. Я читал где-то в документации (я не могу найти его сейчас, я опубликую обновление, когда я это сделаю), что метод можно вызывать только в цикле активного запуска, когда приложение находится в фоновом режиме. У делегата приложения есть активный цикл цикла даже в bg, поэтому вам не нужно создавать свои собственные, чтобы сделать эту работу. [Im не уверен, что это правильное объяснение, но вот как я понял из того, что я читал)

Прежде всего, добавьте объект location для ключа UIBackgroundModes в ваше приложение info.plist. Теперь вам нужно начать обновление местоположения в любом месте вашего приложения:

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

Затем напишите метод обработки обновлений местоположения, скажем -(void)didUpdateToLocation:(CLLocation*)location, в делегате приложения. Затем реализуем метод locationManager:didUpdateLocation:fromLocation из CLLocationManagerDelegate в классе, в котором вы запустили диспетчер местоположений (поскольку мы назначили делегат менеджера местоположений "self" ). Внутри этого метода вам нужно проверить, прошел ли временной интервал, после которого вы должны обрабатывать обновления местоположения. Вы можете сделать это, сохраняя текущее время каждый раз. Если это время истекло, вызовите метод UpdateLocation из вашего делегата приложения:

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

Это вызовет ваш метод каждые 5 минут, даже если ваше приложение находится в фоновом режиме. Imp: эта реализация разряжает батарею, если точность данных местоположения не является критичной, вы должны использовать [locationManager startMonitoringSignificantLocationChanges]

Прежде чем добавлять это в свое приложение, прочитайте Руководство по программированию местоположения по местоположению

Ответ 4

Теперь, когда iOS6 - лучший способ иметь вечно работающие службы определения местоположения,...

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

}

Просто протестируйте его так:

Я запустил приложение, запустил фон и несколько минут переместился в машину. Затем я возвращаюсь домой на 1 час и снова начинаю двигаться (не открывая снова приложение). Места начались снова. Затем остановился на два часа и снова начал. Все в порядке снова...

НЕ ЗАБУДЬТЕ ИСПОЛЬЗОВАТЬ новые службы определения местоположения в iOS6

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

    // Lat/Lon
    float latitudeMe = loc.coordinate.latitude;
    float longitudeMe = loc.coordinate.longitude;
}

Ответ 5

К кому-то еще, имеющему кошмар, это выяснить. У меня есть простое решение.

  • Посмотрите этот пример из raywenderlich.com → введите пример кода, это отлично работает, но, к сожалению, таймер во время фонового местоположения. это будет работать бесконечно.
  • Добавьте таймер, используя:

    -(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];
    
    }
    
  • Просто не забудьте добавить "Регистры приложений для обновлений местоположения" в info.plist.

Ответ 6

К сожалению, все ваши предположения кажутся правильными, и я не думаю, что есть способ сделать это. Чтобы экономить время автономной работы, службы определения местоположения iPhone основаны на движении. Если телефон находится в одном месте, он невидим для служб определения местоположения.

CLLocationManager будет вызывать только locationManager:didUpdateToLocation:fromLocation:, когда телефон получает обновление местоположения, что происходит, только если одна из трех служб определения местоположения (сотовая башня, gps, wifi) воспринимает изменение.

Несколько других вещей, которые могут помочь в дальнейших решениях:

  • Запуск и остановка служб приводит к вызову метода делегата didUpdateToLocation, но newLocation может иметь старую временную метку.

  • Мониторинг регионов может помочь

  • При запуске в фоновом режиме имейте в виду, что может быть сложно получить "полную" поддержку LocationServices, одобренную Apple. Из того, что я видел, они специально разработали startMonitoringSignificantLocationChanges как альтернативу с низким энергопотреблением для приложений, нуждающихся в поддержке фонового местоположения, и настоятельно рекомендуют разработчикам использовать это, если приложение не нуждается в нем.

Удачи!

ОБНОВЛЕНИЕ: Эти мысли могут быть устаревшими. Похоже, что у людей успех с ответом @wjans выше.

Ответ 7

Я написал приложение с помощью служб местоположения, приложение должно отправить местоположение каждые 10 секунд. И это сработало очень хорошо.

Просто используйте allowDeferredLocationUpdatesUntilTraveled: timeout", следуя за документом Apple.

Что я сделал:

Требуется: Зарегистрировать фоновый режим для местоположения обновлений.

1. Создайте LocationManger и startUpdatingLocation, с accuracy и filteredDistance как угодно:

-(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. Чтобы приложение всегда выполнялось с использованием метода allowDeferredLocationUpdatesUntilTraveled:timeout в фоновом режиме, вы должны перезапустить updatingLocation с новым параметром, когда приложение перемещается в фоновый режим, например:

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

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

3. Приложение обновляетсяLocations как обычно с обратным вызовом locationManager:didUpdateLocations::

-(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. Но вы должны обрабатывать данные в обратном вызове locationManager:didFinishDeferredUpdatesWithError: для своей цели

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

     _deferringUpdates = NO;

     //do something 
}

5. ПРИМЕЧАНИЕ. Я думаю, что мы должны reset параметры LocationManager при каждом переключении между фоном и наземным режимом.

Ответ 8

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

Это необходимо для отслеживания фонового местоположения с iOS 9.

Ответ 9

Вот что я использую:

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

Я запускаю отслеживание в AppDelegate следующим образом:

BackgroundLocationManager.instance.start()

Ответ 10

Я использовал метод xs2bush для получения интервала (используя timeIntervalSinceDate) и немного расширил его. Я хотел удостовериться, что я получил требуемую точность, в которой я нуждался, а также то, что я не запускал батарею, сохраняя радиостанцию ​​на более чем необходимо.

Я постоянно работаю в режиме постоянной работы со следующими настройками:

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

это относительно низкий расход батареи. Когда я буду готов прочитать следующее периодическое определение местоположения, сначала проверяю, находится ли местоположение в пределах моей желаемой точности, если это так, я затем использую это местоположение. Если это не так, то я увеличиваю точность с помощью этого:

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

Получите мое местоположение, а затем, как только у меня появится место, я снова вернусь к точности, чтобы свести к минимуму утечку аккумулятора. Я написал полный рабочий пример этого, а также написал источник для кода на стороне сервера, чтобы собрать данные о местоположении, сохранить его в базе данных и разрешить пользователям просматривать данные gps в реальном времени или извлекать и просматривать ранее сохраненные маршруты. У меня есть клиенты для iOS, Android, windows phone и java me. Все клиенты написаны изначально, и все они работают правильно в фоновом режиме. Проект лицензирован MIT.

Проект iOS предназначен для iOS 6 с использованием базового SDK iOS 7. Вы можете получить код здесь.

Пожалуйста, сообщите о проблеме в github, если у вас возникнут проблемы с ней. Спасибо.

Ответ 11

Кажется, что stopUpdatingLocation - это то, что запускает контрольный таймер фона, поэтому я заменил его в файле doUpdateLocation:

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

который, по-видимому, эффективно отключает GPS. Затем селектор для фона NSTimer становится следующим:

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

Все, что я делаю, периодически переключает точность, чтобы получать координаты с высокой точностью каждые несколько минут, и потому что locationManager не был остановлен, backgroundTimeRemaining остается на своем максимальном значении. Это уменьшает потребление батареи от ~ 10% в час (с постоянным kCLLocationAccuracyBest в фоновом режиме) до ~ 2% в час на моем устройстве

Ответ 12

Существует cocoapod APScheduledLocationManager, который позволяет получать обновления фонового местоположения каждые n секунд с желаемой точностью местоположения.

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

В репозитории также содержится пример приложения, написанного в Swift 3.

Ответ 13

Рабочий код (весь поэтапный код)

Шаг 1

  • Перейти к проекту → Возможности → Режимы фона → выберите Обновления местоположения.
  • Перейдите в Project → Info → добавьте ключ NSLocationAlwaysUsageDescription с необязательной строкой.

Шаг 2

Добавьте этот код в AppDelegate.m

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

Шаг 3 Добавьте этот код в метод applicationDidEnterBackground в 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
}

Ответ 14

Прикрепленное решение Swift основано на:

Определите App registers for location updates в файле info.plist

Храните диспетчер locationManager все время

Переключение kCLLocationAccuracy между BestForNavigation (в течение 5 секунд для получения местоположения) и ThreeKilometers для остальной части периода ожидания, чтобы избежать дренажа аккумулятора

Этот пример обновляет местоположение каждые 1 мин в Foreground и каждые 15 минут в фоновом режиме.

Пример отлично работает с Xcode 6 Beta 6, работающим на устройстве iOS 7.

В делете приложения (mapView является необязательным, указывающим на контроллер mapView)

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
}

В mapView (locationManager указывает на объект в 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")
        }
    }