С выпуском iOS 4 платформа Core Location получила значительное обновление благодаря поддержке геозон. Не только приложение может быть уведомлено, когда устройство входит или выходит из геозоны, операционная система также уведомляет, когда приложение находится в фоновом режиме. Это дополнение к структуре Core Location прекрасно вписывается в усилия Apple по поддержке многозадачности на iOS. Это открывает ряд возможностей, таких как выполнение задач в фоновом режиме с помощью геозон, и это именно то, о чем идет речь в этом руководстве.
Что такое геозона?
геозоны
Геозона — это не более чем виртуальная граница, которая соответствует фактическому местоположению в пространстве. В iOS геозона представлена экземпляром класса CLRegion
, который характеризуется координатой (широтой и долготой), радиусом и уникальным идентификатором. Это означает, что геозоны на iOS по определению круговые.
Geofencing
Геозона — это процесс, в котором отслеживается одна или несколько геозон, и действие предпринимается при входе или выходе из конкретной геозоны. Geofencing — технология, очень хорошо подходящая для мобильных устройств. Хорошим примером является приложение Apple Reminders. Вы можете прикрепить геозону к напоминанию, чтобы приложение уведомляло вас, когда вы находитесь в непосредственной близости от определенного места (рисунок 1). Это невероятно мощный и гораздо более интуитивно понятный способ, чем использование традиционных напоминаний, которые напоминают вам на определенную дату или время.
На платформе iOS геозону можно реализовать несколькими способами. Простейшая реализация состоит в том, чтобы использовать инфраструктуру Core Location для мониторинга одной или нескольких геозон. Всякий раз, когда пользовательское устройство входит или выходит из одного из этих геозон, приложение уведомляется операционной системой. Это также работает, если приложение не находится на переднем плане, что крайне важно, если вы хотите правильно реализовать геозону в приложении.
Более сложная реализация геозоны включает удаленный сервер, который отслеживает местоположение устройства пользователя и отправляет push-уведомления на устройство пользователя, если оно входит или выходит из геозоны. Этот подход гораздо более сложный и требует значительных затрат. Одна из причин выбора этой стратегии связана с ограничениями базовой структуры размещения. Позвольте мне объяснить, что я имею в виду под этим.
Геозоны на iOS
Как я уже говорил ранее, платформа Core Location отвечает за геозоны на iOS. Услуги определения местоположения тесно связаны с многозадачностью, и геозоны не являются исключением. Включив геозону в фоновом режиме, создается впечатление, что ваше приложение продолжает работать в фоновом режиме. Однако операционная система продвигается на один шаг дальше, отслеживая интересующие области, даже если приложение неактивно, то есть ни один экземпляр приложения не работает в фоновом или переднем плане. Механизм не такой сложный. Операционная система управляет списком геозон, и какие приложения интересуются, какие геозоны. Если определенная геозона введена или закрыта, операционная система уведомляет соответствующее приложение. Основной причиной такого подхода является экономия энергии аккумулятора. Вместо того, чтобы разрешить нескольким приложениям работать в фоновом режиме и использовать службы определения местоположения устройства, операционная система управляет этой задачей и уведомляет приложение только при необходимости.
Есть несколько недостатков в том, как работает геозона на iOS. Например, приложение может контролировать не более двадцати регионов или геозон. Этого может быть достаточно для большинства приложений, но вы можете себе представить, что для некоторых приложений этот верхний предел неприемлем.
В оставшейся части этого урока я покажу вам, как реализовать геозону в приложении для iOS. В конце урока я сделаю несколько предложений о том, как использовать геозону для выполнения задач в фоновом режиме, даже если ваше приложение не запущено.
Шаг 1: Настройка проекта
Создайте новый проект в XCode, выбрав шаблон приложения Single View Application из списка шаблонов (рисунок 2). Назовите свое приложение Geofencing , введите идентификатор компании, установите iPhone для семейства устройств и установите флажок Использовать раскадровки и Использовать автоматический подсчет ссылок . Мы не будем использовать модульные тесты в этом проекте (рисунок 3). Сообщите Xcode, где вы хотите сохранить проект, и нажмите « Создать» .
Поскольку платформа Core Location сделает тяжелую работу для нас, мы должны связать наш проект с ней. Выберите проект в Навигаторе проектов и выберите цель Geofencing из списка целей. Выберите вкладку Build Phases вверху и откройте панель Link Binary With Libraries . Нажмите кнопку со знаком плюс и выберите CoreLocation.framework из списка библиотек и фреймворков (рисунок 4).
Шаг 2: Пользовательский интерфейс
Прежде чем мы создадим пользовательский интерфейс, нам нужно сделать одно изменение в классе MTViewController
. Откройте файл заголовка класса и измените суперкласс на UITableViewController
.
1
2
3
4
5
|
#import <UIKit/UIKit.h>
@interface MTViewController : UITableViewController
@end
|
С помощью раскадровки создание пользовательского интерфейса происходит быстро и легко. Откройте MainStoryboard.storyboard , выберите контроллер представления и удалите его из раскадровки. Перетащите контроллер табличного представления из библиотеки объектов справа и установите его класс MTViewController
в Identity Inspector . Пока контроллер вида выбран, откройте меню « Редактор» и выберите « Вставить»> «Контроллер навигации» (рисунок 5).
Выберите ячейку прототипа контроллера табличного представления и присвойте ему идентификатор GeofenceCell и установите для стиля ячейки значение Subtitle (рисунок 6).
Приложению понадобится возможность добавлять и удалять геозоны. Давайте начнем с создания двух кнопок. В методе viewDidLoad
контроллера viewDidLoad
мы вызываем setupView
, вспомогательный метод, в котором мы дополнительно настраиваем пользовательский интерфейс. В setupView
мы создаем кнопку для добавления геозоны на основе текущего местоположения и кнопку для редактирования представления таблицы.
1
2
3
4
5
6
|
— (void)viewDidLoad {
[super viewDidLoad];
// Setup View
[self setupView];
}
|
1
2
3
4
5
6
7
|
— (void)setupView {
// Create Add Button
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addCurrentLocation:)];
// Create Edit Button
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@»Edit», nil) style:UIBarButtonItemStyleBordered target:self action:@selector(editTableView:)];
}
|
Шаг 3: Инициализация
Класс CLLocationManager
является звездным игроком этого урока. Менеджер местоположения предоставит нам координаты текущего местоположения устройства, а также позволит нам работать с геозонами. Начните с добавления оператора import в заголовочный файл контроллера представления, чтобы импортировать заголовочные файлы платформы.
1
2
3
4
5
6
|
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
@interface MTViewController : UITableViewController
@end
|
Нам также нужно создать две частные свойства. Первое свойство хранит ссылку на менеджер местоположения, экземпляр класса CLLocationManager
. Чтобы получать обновления от диспетчера местоположений, класс MTViewController
должен соответствовать протоколу CLLocationManagerDelegate
. Я расскажу об этом подробнее через несколько минут. Второе свойство имеет тип NSArray
и служит источником данных табличного представления. Он будет хранить геозоны, экземпляры CLRegion
, которые контролирует менеджер местоположений. По практическим причинам я также объявил вспомогательную переменную ( BOOL
) с именем _didStartMonitoringRegion
. Его цель станет ясна чуть позже в этом уроке.
1
2
3
4
5
6
7
8
|
@interface MTViewController () <CLLocationManagerDelegate> {
BOOL _didStartMonitoringRegion;
}
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) NSMutableArray *geofences;
@end
|
В методе awakeFromNib
контроллера awakeFromNib
мы инициализируем и настраиваем менеджер местоположения, как показано ниже. Метод awakeFromNib
отправляется в контроллер представления после того, как каждый объект в nib-файле загружен и готов к использованию.
01
02
03
04
05
06
07
08
09
10
|
— (void)awakeFromNib {
[super awakeFromNib];
// Initialize Location Manager
self.locationManager = [[CLLocationManager alloc] init];
// Configure Location Manager
[self.locationManager setDelegate:self];
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
}
|
Шаг 4: Заполнение табличного представления
Чтобы заполнить табличное представление, нам нужны геозоны, которые операционная система отслеживает для нас. Менеджер местоположений сохраняет ссылку на эти геозоны, что означает, что нет необходимости хранить геозоны самостоятельно. awakeFromNib
метод awakeFromNib
как показано ниже. Поскольку свойство monitoredRegions
является набором, необходимо сохранить ссылку на свойство geofences
диспетчера местоположений в свойстве geofences
контроллера представления.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
— (void)awakeFromNib {
[super awakeFromNib];
// Initialize Location Manager
self.locationManager = [[CLLocationManager alloc] init];
// Configure Location Manager
[self.locationManager setDelegate:self];
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyHundredMeters];
// Load Geofences
self.geofences = [NSMutableArray arrayWithArray:[[self.locationManager monitoredRegions] allObjects]];
}
|
Реализация протокола источника данных табличного представления не вызывает удивления (см. Ниже). Как я упоминал ранее, свойство geofences
содержит экземпляры CLRegion
. В каждой ячейке табличного представления мы отображаем местоположение каждого региона вместе с уникальным идентификатором региона. Далее мы подробнее рассмотрим tableView:commitEditingStyle:forRowAtIndexPath:
метод позже в этом руководстве.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.geofences ?
}
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.geofences count];
}
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:GeofenceCellIdentifier];
// Fetch Geofence
CLRegion *geofence = [self.geofences objectAtIndex:[indexPath row]];
// Configure Cell
CLLocationCoordinate2D center = [geofence center];
NSString *text = [NSString stringWithFormat:@»%.1f | %.1f», center.latitude, center.longitude];
[cell.textLabel setText:text];
[cell.detailTextLabel setText:[geofence identifier]];
return cell;
}
— (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
— (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
|
Не забудьте объявить статический идентификатор ячейки в верхней части файла реализации контроллера представления, как показано ниже.
1
|
static NSString *GeofenceCellIdentifier = @»GeofenceCell»;
|
Шаг 5: Добавление геозоны
Чтобы добавить геозону, нам нужно реализовать действие addCurrentLocation:
. Его реализация довольно проста, как вы можете видеть. В addCurrentLocation:
мы просим экземпляр менеджера местоположений начать генерировать обновления для текущего местоположения устройства. Мы также установили вспомогательную переменную экземпляра _didStartMonitoringRegion
в значение NO
. Я объясню причину этого через мгновение.
1
2
3
4
5
6
7
|
— (void)addCurrentLocation:(id)sender {
// Update Helper
_didStartMonitoringRegion = NO;
// Start Updating Location
[self.locationManager startUpdatingLocation];
}
|
Всякий раз, когда у менеджера местоположения есть доступные обновления местоположения, он вызывает locationManager:didUpdateLocations:
метод делегата, как показано ниже. В этом методе делегата мы сначала проверяем, установлено ли для _didStartMonitoringRegion
значение NO
. Это важно, потому что может случиться так, что обновления местоположения отправляются так быстро один за другим, что для одного местоположения создается несколько геозон. Мы создаем регион или CLRegion
( CLRegion
) на основе первого местоположения в массиве местоположений, предоставляемых нам менеджером местоположений. Радиус региона установлен на 250.0
(в метрах), но вы можете изменить его в соответствии со своими потребностями.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
— (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if (locations && [locations count] && !_didStartMonitoringRegion) {
// Update Helper
_didStartMonitoringRegion = YES;
// Fetch Current Location
CLLocation *location = [locations objectAtIndex:0];
// Initialize Region to Monitor
CLRegion *region = [[CLRegion alloc] initCircularRegionWithCenter:[location coordinate] radius:250.0 identifier:[[NSUUID UUID] UUIDString]];
// Start Monitoring Region
[self.locationManager startMonitoringForRegion:region];
[self.locationManager stopUpdatingLocation];
// Update Table View
[self.geofences addObject:region];
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:([self.geofences count] — 1) inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
// Update View
[self updateView];
}
}
|
Идентификатор экземпляра CLRegion
гарантирует, что мы можем отличить одну отслеживаемую область от другой. Если бы мы отслеживали новый регион с тем же идентификатором, старый регион был бы заменен новым регионом. Чтобы начать мониторинг нового региона, мы отправляем менеджеру местоположения сообщение startMonitoringForRegion:
и передаем новый регион в качестве аргумента. При отслеживании нового региона мы также прекращаем обновление текущего местоположения.
Чтобы обновить табличное представление, мы сначала обновляем его источник данных, а затем вставляем строку внизу табличного представления. Представление также обновляется путем вызова другого вспомогательного метода updateView
. Этот метод выполняет несколько проверок и обновляет представление контроллера представления соответственно.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
— (void)updateView {
if (![self.geofences count]) {
// Update Table View
[self.tableView setEditing:NO animated:YES];
// Update Edit Button
[self.navigationItem.rightBarButtonItem setEnabled:NO];
[self.navigationItem.rightBarButtonItem setTitle:NSLocalizedString(@»Edit», nil)];
} else {
// Update Edit Button
[self.navigationItem.rightBarButtonItem setEnabled:YES];
}
// Update Add Button
if ([self.geofences count] < 20) {
[self.navigationItem.leftBarButtonItem setEnabled:YES];
} else {
[self.navigationItem.leftBarButtonItem setEnabled:NO];
}
}
|
Всякий раз, когда устройство пользователя входит и выходит из отслеживаемой области, делегату менеджера местоположения отправляется сообщение locationManager:didEnterRegion:
и locationManager:didExitRegion:
соответственно. Реализуйте оба метода делегата, как показано ниже.
1
2
3
4
5
6
7
|
— (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
NSLog(@»%s», __PRETTY_FUNCTION__);
}
— (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
NSLog(@»%s», __PRETTY_FUNCTION__);
}
|
Перед запуском приложения в iOS Simulator нам нужно изменить активную схему. Нажмите Geofencing справа от кнопки Stop (рисунок 7) и выберите Edit Scheme … из меню. Слева выберите вкладку Run Geofencing.app и выберите вкладку Options вверху (рисунок 8). Установите флажок « Разрешить моделирование местоположения» и установите местоположение по умолчанию в Нью-Йорке, штат Нью-Йорк, США (рисунок 8). Это облегчит тестирование нашего приложения.
Создайте и запустите приложение в iOS Simulator и добавьте регион для мониторинга, нажав кнопку добавления в левом верхнем углу. Вы можете проверить геозону, изменив симулированное местоположение симулятора iOS (рисунок 9).
Шаг 6: Редактирование геозон
Редактировать геозоны довольно просто. Начните с реализации действия editTableView:
как показано ниже. Все, что мы делаем, это переключаем режим редактирования табличного представления и обновляем заголовок кнопки редактирования.
01
02
03
04
05
06
07
08
09
10
11
|
— (void)editTableView:(id)sender {
// Update Table View
[self.tableView setEditing:![self.tableView isEditing] animated:YES];
// Update Edit Button
if ([self.tableView isEditing]) {
[self.navigationItem.rightBarButtonItem setTitle:NSLocalizedString(@»Done», nil)];
} else {
[self.navigationItem.rightBarButtonItem setTitle:NSLocalizedString(@»Edit», nil)];
}
}
|
Чтобы удалить геозону из табличного представления (и прекратить мониторинг региона), мы реализуем tableView:commitEditingStyle:forRowAtIndexPath:
как показано ниже. Его реализация не сложна, как вы можете видеть. Мы выбираем соответствующую область из источника данных, сообщаем менеджеру местоположения прекратить мониторинг этой области, обновляем табличное представление и его источник данных и обновляем представление. Создайте и запустите приложение еще раз, чтобы попробовать его.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
— (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Fetch Monitored Region
CLRegion *region = [self.geofences objectAtIndex:[indexPath row]];
// Stop Monitoring Region
[self.locationManager stopMonitoringForRegion:region];
// Update Table View
[self.geofences removeObject:region];
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
// Update View
[self updateView];
}
}
|
Немного взлома
Разработчики пытались обойти ограничения операционной системы iOS на протяжении многих лет. Одним из способов взлома, который некоторые приложения используют для обновления приложения в фоновом режиме, является геозона. Это очевидная стратегия для приложений, таких как Foursquare или TomTom. Однако Apple, похоже, позволяет другим приложениям использовать геозону. Например, Instapaper реализовал эту стратегию некоторое время назад. Определяя одну или несколько геозон, Instapaper начинает обновлять список сохраненных статей в фоновом режиме, когда вы вводите одну из этих геозон. Это умная стратегия, которая может быть полезна в ряде случаев использования.
Статус бар
Когда приложение активно использует службы определения местоположения устройства, в строке состояния отображается значок служб определения местоположения. То же самое относится и к геозонам. Разница заключается в значке, показанном на рисунке 10. Даже если на переднем плане не запущено ни одно приложение, выделенный значок служб определения местоположения виден, если какое-либо приложение на устройстве использует геозону.
Что нужно помнить
В этом уроке я рассмотрел только абсолютные основы геозоны. Важно помнить несколько вещей, прежде чем внедрять геозону в любом приложении. Самый очевидный аспект — это конфиденциальность. Пользователь может отключить службы определения местоположения для любого приложения, а это означает, что геозона не гарантируется в каждом случае использования. При работе со службами определения местоположения важно выполнить три проверки: (1) включены ли службы определения местоположения, (2) предоставил ли пользователь ваше приложение для использования служб определения местоположения устройства и (3) поддерживает ли устройство геозону.
CLLocationManager
на первый вопрос — отправка классу CLLocationManager
сообщения locationServicesEnabled
. Это сообщает приложению, включены ли на устройстве службы определения местоположения. Чтобы проверить, разрешено ли вашему приложению использовать службы определения местоположения устройства, вы отправляете CLLocationManager
сообщение locationServicesEnabled
. Наконец, чтобы проверить, доступно ли геозонирование на устройстве, вызовите CLLocationManager
класса CLLocationManager
. Не все устройства и модели поддерживают геозоны.
Вывод
Реализация геозоны в приложении для iOS не так сложна, как вы можете видеть. Базовое понимание инфраструктуры Core Location — это все, что вам нужно для начала. Базовая инфраструктура расположения — это мощная инфраструктура, которая имеет гораздо больше полезных функций, которые часто упускаются из виду или недоиспользуются.