Статьи

Форвард Геокодирование с CLGeocoder

С появлением iOS 5 разработчикам iOS стали доступны сотни новых API. Одной из наименее известных функций было добавление API геокодирования в качестве части платформы Core Location. Класс, обрабатывающий запросы геокодирования, — CLGeocoder . В следующие двадцать минут я покажу вам, как создать приложение, которое преобразует физический адрес в пару координат с помощью CLGeocoder .

Что такое геокодирование? Геокодирование — это модное слово, связывающее пару координат с физическим адресом (обратное геокодирование) и наоборот (прямое геокодирование). Несмотря на то, что MapKit имеет возможность MKReverseGeocoder координаты геокодирования с помощью MKReverseGeocoder с момента выпуска iOS 3, MKReverseGeocoder устарела с iOS 5. CLGeocoder обрабатывает геокодирование в iOS 5 и делает это простым и элегантным способом. Как видно из его названия, CLGeocoder является частью мощной платформы Core Location. В дополнение к обратному геокодированию, CLGeocoder может также преобразовывать физические адреса в местоположения (прямое геокодирование), и это именно то, что мы будем делать в этом руководстве.

Мы создадим приложение, которое позволит пользователю вводить улицу, город и страну, и наше приложение будет возвращать широту и долготу для адреса, а также возможное название местоположения, так называемую область интересов.

Как CLGeocoder делает это? Инфраструктура Core Location подключается к веб-сервису за кулисами, но вам, как разработчику, не нужно иметь дело с мельчайшими подробностями. CLGeocoder очень прост в использовании.


Запустите Xcode и создайте новый проект, выбрав шаблон приложения Single View . Назовите геокодирование приложения, введите идентификатор компании, выберите iPhone для семейства устройств и обязательно установите флажок « Использовать автоматический подсчет ссылок» . Вы можете оставить поле « Префикс класса» пустым, а оставшиеся флажки снятыми. Выберите место для сохранения вашего проекта и нажмите « Создать» .

Геокодирование с основным местоположением: настройка проекта - рисунок 1
Геокодирование с основным местоположением: настройка проекта - рисунок 2

Мы начинаем с создания пользовательского интерфейса нашего приложения. Однако перед открытием файла XIB контроллера представления нам нужно создать шесть выходов и одно действие. Выберите файл заголовка вашего контроллера представления и объявите выходы и действия, как показано во фрагменте ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
#import <UIKit/UIKit.h>
 
@interface ViewController : UIViewController {
    __weak UITextField *_streetField;
    __weak UITextField *_cityField;
    __weak UITextField *_countryField;
    __weak UIButton *_fetchCoordinatesButton;
    __weak UILabel *_nameLabel;
    __weak UILabel *_coordinatesLabel;
}
 
@property (nonatomic, weak) IBOutlet UITextField *streetField;
@property (nonatomic, weak) IBOutlet UITextField *cityField;
@property (nonatomic, weak) IBOutlet UITextField *countryField;
@property (nonatomic, weak) IBOutlet UIButton *fetchCoordinatesButton;
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@property (nonatomic, weak) IBOutlet UILabel *coordinatesLabel;
 
— (IBAction)fetchCoordinates:(id)sender;
 
@end

Первые три выхода — это экземпляры UITextField в которых пользователь может ввести улицу, город и страну. Четвертый выход — это экземпляр UIButton , который запускает наше действие, когда пользователь касается его. Последние два выхода — это экземпляры UILabel которые мы будем использовать для отображения результатов нашего запроса геокодирования. Если наш запрос геокодирования вернется успешно, мы отобразим название местоположения в первой метке (подробнее об этом позже) и координаты местоположения во второй метке. Не волнуйтесь, если это вас смущает. Это будет иметь больше смысла, как только мы подключим все в нашем файле xib.

Мы также объявляем один метод, который будет (1) запускать и (2) обрабатывать наш запрос геокодирования. Это действие будет подключено к нашей кнопке. Вы задаетесь вопросом, зачем нам нужен выход для нашей кнопки? Я расскажу вам больше об этом в конце этого урока.

Не забудьте синтезировать аксессуары для розеток. Вам также следует создать пустую реализацию нашего действия, чтобы избежать предупреждений компилятора.

1
2
3
4
5
@synthesize streetField = _streetField, cityField = _cityField, countryField = _countryField, fetchCoordinatesButton = _fetchCoordinatesButton, nameLabel = _nameLabel, coordinatesLabel = _coordinatesLabel;
 
— (IBAction)fetchCoordinates:(id)sender {
    NSLog(@»Fetch Coordinates»);
}

Готов? Перейдите к xib-файлу нашего контроллера представления и перетащите три текстовых поля, одну кнопку и две метки в представление вашего контроллера представления. Расположите их, как показано на рисунке ниже, и дайте кнопке заголовок « Выбрать координаты», чтобы пользователь знал, что произойдет, если нажать кнопку.

Геокодирование с расположением ядра: настройка интерфейса пользователя - рисунок 3

Не забудьте добавить заполнитель в каждое текстовое поле, чтобы пользователь знал, какой тип информации ожидает каждое текстовое поле. Я также настроил метки, чтобы белый текст на синем фоне выделялся.

Посмотрим что у нас пока. Пользователь может ввести улицу и номер в первом текстовом поле, город во втором текстовом поле и страну в третьем текстовом поле. Когда пользователь нажимает кнопку « Получить координаты» , наше приложение отправляет запрос геокодирования на введенный пользователем адрес. Если наш запрос выполнен успешно, мы отображаем название местоположения и координаты (широта и долгота) в метках.

С пользовательским интерфейсом мы готовы подключить наши магазины и действия. Для выходов нажмите клавишу управления и перетащите от Владельца файла до текстовых полей и выберите соответствующий IBOutlet из всплывающего меню. Сделайте то же самое для кнопки и ярлыков. Для действия нажмите клавишу управления еще раз и перетащите от нашей кнопки к Владельцу файла и выберите метод fetchCoordinates: из меню, которое появляется. Это подключит наше действие к событию UIControlEventTouchUpInside кнопки, и это именно то, что мы хотим.

Геокодирование с расположением ядра: настройка интерфейса пользователя - рисунок 4

Прежде чем мы начнем реализовывать метод fetchCoordinates:, нам нужно добавить базовую платформу Location в наш проект. Выберите наш проект в Навигаторе проектов и выберите единственную цель в списке целей. Вверху выберите вкладку Build Phases и откройте панель Link Binary With Libraries . Нажмите знак плюс и выберите Core Location из появившегося списка. Наш проект теперь связан с базовой структурой расположения.

Изучение CLGeocoder: добавление основного местоположения в The Mix

Есть еще одна вещь, которую нам нужно сделать, прежде чем мы сможем использовать базовую платформу Location. Вернитесь к заголовочному файлу нашего контроллера представления и добавьте новый оператор импорта под оператором импорта UIKit.

1
2
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

Оператор import импортирует заголовки платформы Core Location и гарантирует, что мы можем использовать его функциональные возможности в нашем контроллере представления. Нам также необходимо создать переменную экземпляра для объекта геокодера, который мы будем использовать для выполнения запросов геокодирования. Как я упоминал в начале этого урока, для этой цели мы будем использовать экземпляр CLGeocoder . Добавьте переменную экземпляра и свойство в заголовочный файл вашего контроллера представления и не забудьте синтезировать его методы доступа в файле реализации вашего контроллера представления. Теперь мы готовы сделать немного магии.

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
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
 
@interface ViewController : UIViewController {
    CLGeocoder *_geocoder;
     
    __weak UITextField *_streetField;
    __weak UITextField *_cityField;
    __weak UITextField *_countryField;
    __weak UIButton *_fetchCoordinatesButton;
    __weak UILabel *_nameLabel;
    __weak UILabel *_coordinatesLabel;
}
 
@property (nonatomic, strong) CLGeocoder *geocoder;
 
@property (nonatomic, weak) IBOutlet UITextField *streetField;
@property (nonatomic, weak) IBOutlet UITextField *cityField;
@property (nonatomic, weak) IBOutlet UITextField *countryField;
@property (nonatomic, weak) IBOutlet UIButton *fetchCoordinatesButton;
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@property (nonatomic, weak) IBOutlet UILabel *coordinatesLabel;
 
— (IBAction)fetchCoordinates:(id)sender;
 
@end
1
2
3
4
5
6
7
// Remember to synthesize the geocoder:
@synthesize geocoder = _geocoder;
 
 
— (IBAction)fetchCoordinates:(id)sender {
    NSLog(@»Fetch Coordinates»);
}

Я пойду через fetchCoordinates: метод, шаг за шагом. Сначала мы проверяем, установлен ли наш экземпляр геокодера. Если это не так, мы инициализируем его. Часто хорошей практикой является инициализация объекта только тогда, когда он вам действительно нужен.

1
2
3
if (!self.geocoder) {
    self.geocoder = [[CLGeocoder alloc] init];
}

В этом примере мы сделаем запрос на прямое геокодирование, что означает, что мы отправляем адрес веб-службе, с которой взаимодействует Базовое расположение, и он отправляет нам данные о местоположении. Метод, который мы будем использовать, принимает строку адреса, что означает, что нам нужно объединить данные адреса из наших текстовых полей.

1
NSString *address = [NSString stringWithFormat:@»%@ %@ %@», self.streetField.text, self.cityField.text, self.countryField.text];

Наконец, мы вызываем geocodeAddressString: completeHandler: для нашего объекта геокодера . Этот метод принимает два аргумента: (1) нашу адресную строку и (2) блок завершения. Это еще одно аккуратное применение блоков, которое демонстрирует силу, которую они используют.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
[self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
    if ([placemarks count] > 0) {
        CLPlacemark *placemark = [placemarks objectAtIndex:0];
        CLLocation *location = placemark.location;
        CLLocationCoordinate2D coordinate = location.coordinate;
         
        self.coordinatesLabel.text = [NSString stringWithFormat:@»%f, %f», coordinate.latitude, coordinate.longitude];
         
        if ([placemark.areasOfInterest count] > 0) {
            NSString *areaOfInterest = [placemark.areasOfInterest objectAtIndex:0];
            self.nameLabel.text = areaOfInterest;
        } else {
            self.nameLabel.text = @»No Area of Interest Was Found»;
        }
    }
}];

Блок завершения принимает два аргумента: (1) массив местоположений (так называемые метки) и (2) ошибку в случае, если что-то пойдет не так. Почему мы получаем массив локаций вместо одного? Когда адрес, который мы отправляем веб-службе, недостаточно конкретен, возможно, веб-служба возвращает не одну, а несколько меток, соответствующих этому адресу. Несколько чего? CLPlacemark , экземпляр CLPlacemark , является контейнером для данных, связанных с парой координат. Он содержит больше, чем просто координаты, такие как улица, город и страна, а также достопримечательности, такие как здания, национальные парки и исторические памятники.

Для нашего проекта нам нужна только координата метки и области интереса, если она есть. Я рекомендую вам записать весь массив меток на консоль, чтобы увидеть, что он содержит. Это всегда хороший способ изучить новые API.

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

Одним из свойств экземпляра CLPlacemark является его местоположение, которое является объектом CLLocation . Если вы не знакомы с объектами CLLocation , они содержат CLLocationCoordinate2D нами координату ( CLLocationCoordinate2D ), а также показатель точности местоположения. Объекты CLLocation используются во всей структуре Core Location и удивительно полезны.

Чтобы отобразить результат нашего запроса на экране, мы берем широту и долготу свойства местоположения метки и отображаем его в нашей метке. Мы также проверяем, не является ли массив областей интереса метки непустым. Если это так, то мы берем первый содержащийся в нем объект (экземпляр NSString ) и отображаем его в нашей первой метке. Если интересующие области не были найдены, мы сообщаем пользователю, отображая простое сообщение.

Я хочу добавить последний штрих к нашему приложению, чтобы улучшить взаимодействие с пользователем, а также следовать рекомендациям Apple. Когда мы делаем запрос геокодирования, мы не получаем немедленного ответа. Как я упоминал ранее, базовая структура местоположения взаимодействует с веб-службой и получает ответ. Когда приходит ответ, зависит от различных факторов, таких как скорость нашего сетевого подключения. Документация CLGeocoder гласит, что запросы к веб-сайту следует делать экономно. Другими словами, пользователь (1) не должен делать несколько запросов за короткий промежуток времени, нажимая кнопку несколько раз, и (2) пользователь не должен иметь возможность сделать запрос до того, как активный запрос вернет ответ. Чтобы выполнить последнее, мы отключаем кнопку до тех пор, пока запрос не будет завершен (успешно или безуспешно). Для этого мы отключаем кнопку перед тем, как сделать запрос, и снова активируем кнопку в блоке завершения. Посмотрите на полную реализацию наших fetchCoordinates: метод для разъяснения. Запустите ваше приложение и введите адрес, чтобы пройти через него.

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
— (IBAction)fetchCoordinates:(id)sender {
    if (!self.geocoder) {
        self.geocoder = [[CLGeocoder alloc] init];
    }
     
    NSString *address = [NSString stringWithFormat:@»%@ %@ %@», self.streetField.text, self.cityField.text, self.countryField.text];
     
    self.fetchCoordinatesButton.enabled = NO;
     
    [self.geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
        if ([placemarks count] > 0) {
            CLPlacemark *placemark = [placemarks objectAtIndex:0];
            CLLocation *location = placemark.location;
            CLLocationCoordinate2D coordinate = location.coordinate;
             
            self.coordinatesLabel.text = [NSString stringWithFormat:@»%f, %f», coordinate.latitude, coordinate.longitude];
             
            if ([placemark.areasOfInterest count] > 0) {
                NSString *areaOfInterest = [placemark.areasOfInterest objectAtIndex:0];
                self.nameLabel.text = areaOfInterest;
            }
        }
         
        self.fetchCoordinatesButton.enabled = YES;
    }];
}

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


CLGeocoder расположение стало очень мощным фреймворком, и CLGeocoder — это только один из многих классов, которые помогут вам выполнить сложную задачу с легкостью и без особых накладных расходов. Наслаждайтесь!