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

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

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

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

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

15 ответов

Решение

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

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

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

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

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

  1. Зайдите в Проект -> Возможности -> Фоновые режимы -> выберите Обновления местоположения

  2. Перейдите в Project -> Info -> добавьте ключ NSLocationAlwaysUsageDescription с пустым значением (или, необязательно, любым текстом)

  3. Чтобы ваше местоположение работало, когда ваше приложение находится в фоновом режиме, и отправляйте координаты веб-службе или делайте с ними что-нибудь каждые 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

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

Прежде всего, добавьте 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]

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

Теперь, когда 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;
}

Для кого-то еще с кошмаром выяснить это. У меня есть простое решение.

  1. посмотрите этот пример с raywenderlich.com-> есть пример кода, он работает отлично, но, к сожалению, нет таймера в фоновом режиме. это будет работать бесконечно.
  2. Добавить таймер с помощью:

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

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

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 секунд. И это сработало очень хорошо.

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

Что я сделал:

Обязательно: зарегистрировать фоновый режим для обновления Location.

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. Приложение получает обновленные местоположения как обычно с 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. ПРИМЕЧАНИЕ: я думаю, что мы должны сбросить параметры LocationManager каждый раз приложение переключается между фоновым / передним режимом.

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

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

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

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

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

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

Удачи!

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

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

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

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

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

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, если вы видите какие-либо проблемы с ним. Благодарю.

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

     [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% в час на моем устройстве

Есть кокосовый модуль https://github.com/paleksandrs/APScheduledLocationManager, который позволяет получать фоновые обновления местоположения каждые n секунд с желаемой точностью определения местоположения.

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

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

Рабочий код (весь пошаговый код)

Шаг 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
}

В iOS 9 и watchOS 2.0 в CLLocationManager есть новый метод, который позволяет запрашивать текущее местоположение: CLLocationManager:requestLocation(). Это завершается немедленно, а затем возвращает местоположение делегату CLLocationManager.

Вы можете использовать NSTimer для запроса местоположения каждую минуту с этим методом сейчас, и вам не нужно работать с методами startUpdatingLocation и stopUpdatingLocation.

Однако, если вы хотите захватывать местоположения на основе изменения X метров от последнего местоположения, просто установите для свойства distanceFilter CLLocationManger и значение X для startUpdatingLocation().

Прилагается решение Swift, основанное на:

определять App registers for location updates в info.plist

Постоянно работайте с locationManager

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

Этот пример обновляет местоположение каждые 1 минуту на переднем плане и каждые 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")
        }
    }
Другие вопросы по тегам