Как мне получать фоновое обновление местоположения каждые 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 минут сделайте следующее:
Зайдите в Проект -> Возможности -> Фоновые режимы -> выберите Обновления местоположения
Перейдите в 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
Я сделал это в приложении, которое я разрабатываю. Таймеры не работают, когда приложение находится в фоновом режиме, но приложение постоянно получает обновления местоположения. Я прочитал где-то в документации (я не могу найти его сейчас, я опубликую обновление, когда я это сделаю), что метод может быть вызван только в активном цикле выполнения, когда приложение находится в фоновом режиме. У делегата приложения есть активный цикл выполнения даже в 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;
}
Для кого-то еще с кошмаром выяснить это. У меня есть простое решение.
- посмотрите этот пример с 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.
Вот что я использую:
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")
}
}