Текст книги "iOS. Приемы программирования"
Автор книги: Вандад Нахавандипур
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 34 (всего у книги 59 страниц)
Фреймворк Core Location, входящий в состав комплекта SDK, предоставляет программисту функционал, который позволяет определять актуальное положение устройства с системой iOS в пространстве. Поскольку в iOS пользователь может отключать определение местоположения в разделе Settings (Настройки), то мы перед тем, как инстанцировать объект типа CLLocationManager, проверим, работают ли на устройстве геолокационные службы.
Объект, являющийся делегатом CLLocationManager, должен соответствовать протоколу CLLocationManagerDelegate.
Вот как мы объявим объект нашего диспетчера местоположения в. h-файле контроллера вида (создавать экземпляр CLLocationManager может и объект, не являющийся контроллером вида):
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
@interface ViewController () <CLLocationManagerDelegate>
@property (nonatomic, strong) CLLocationManager *myLocationManager;
@end
@implementation ViewController
Контроллер нашего вида будет иметь следующую реализацию:
– (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation{
/* Получена информация о новом местоположении. */
NSLog(@"Latitude = %f", newLocation.coordinate.latitude);
NSLog(@"Longitude = %f", newLocation.coordinate.longitude);
}
– (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error{
/* Не удалось получить информацию о местоположении пользователя. */
}
– (void)viewDidLoad {
[super viewDidLoad];
if ([CLLocationManager locationServicesEnabled]){
self.myLocationManager = [[CLLocationManager alloc] init];
self.myLocationManager.delegate = self;
[self.myLocationManager startUpdatingLocation];
} else {
/* Геолокационные службы не активизированы.
Попробуйте исправить ситуацию: например предложите пользователю
включить геолокационные службы. */
NSLog(@"Location services are not enabled");
}
}
Метод экземпляра startUpdateLocation, относящийся к классу CLLocationManager, сообщает делегату о том, удалось или нет получить информацию о местоположении пользователя. Это делается с помощью методов location Manager: didUpdateToLocation: fromLocation: и locationManager: didFailWithError: объекта делегата, именно в таком порядке.
9.4. Отображение маркеров в картографическом виде
Постановка задачиНеобходимо указать пользователю конкретное место на карте.
РешениеВоспользуйтесь встроенными аннотациями для картографических видов. Для этого выполните следующие шаги.
1. Создайте новый класс и назовите его MyAnnotation.
2. Убедитесь, что этот класс соответствует протоколу MKAnnotation.
3. Определите свойство типа CLLocationCoordinate2D для этого класса и назовите данное свойство coordinate. Убедитесь, что задали это свойство как readonly (только для чтения), поскольку свойство coordinate в соответствии с протоколом MKAnnotation определяется как readonly.
4. Далее можно (но не обязательно) определить два свойства типа NSString, а именно title и subtitle, которые могут содержать заголовок и подзаголовок вашего аннотирующего вида. Оба этих свойства также будут readonly.
5. Создайте для вашего класса метод-инициализатор. Этот метод будет принимать параметр типа CLLocationCoordinate2D. В этом методе присвойте переданный параметр местоположения тому свойству, которое мы определили на этапе 3. Поскольку это свойство является readonly, его невозможно присвоить с помощью кода вне области видимости данного класса. Следовательно, инициализатор этого класса действует здесь как перемычка и позволяет опосредованно присваивать значение этому свойству. Такие же операции мы осуществим со свойствами title и subtitle.
6. Инстанцируйте класс MyAnnotation и добавьте его к вашей карте с помощью метода addAnnotation:, относящегося к классу MKMapView.
ОбсуждениеКак было рассказано в подразделе «Решение» данного раздела, нам следует создать объект, соответствующий протоколу MKAnnotation, а позже инстанцировать этот объект и передать ему карту для отображения. h-файл этого объекта будет записываться так:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface MyAnnotation: NSObject <MKAnnotation>
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, copy, readonly) NSString *subtitle;
– (instancetype)initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString *)paramTitle
subTitle:(NSString *)paramSubTitle;
@end
В.m-файле класса MyAnnotation мы создаем класс, отвечающий за отображение геолокационной информации, и делаем это следующим образом:
#import «MyAnnotation.h»
@implementation MyAnnotation
– (instancetype)initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString *)paramTitle
subTitle:(NSString *)paramSubTitle{
self = [super init];
if (self!= nil){
coordinate = paramCoordinates;
title = paramTitle;
subtitle = paramSubTitle;
}
return(self);
}
@end
Позже мы инстанцируем этот класс и добавим его к нашей карте, например к. m-файлу того контроллера вида, который создает и отображает картографический вид:
#import «ViewController.h»
#import «MyAnnotation.h»
#import <MapKit/MapKit.h>
@interface ViewController () <MKMapViewDelegate>
@property (nonatomic, strong) MKMapView *myMapView;
@end
@implementation ViewController
– (void)viewDidLoad {
[super viewDidLoad];
/* Создаем карту такого же размера, как и наш вид. */
self.myMapView = [[MKMapView alloc]
initWithFrame: self.view.bounds];
self.myMapView.delegate = self;
/* Задаем для карты тип Standard. */
self.myMapView.mapType = MKMapTypeStandard;
self.myMapView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
/* Добавляем ее к нашему виду. */
[self.view addSubview: self.myMapView];
/* Это просто один образец местоположения. */
CLLocationCoordinate2D location =
CLLocationCoordinate2DMake(50.8219 16929 07181, -0.13 81176 71012 87842);
/* Создаем аннотацию, используя информацию о местоположении. */
MyAnnotation *annotation =
[[MyAnnotation alloc] initWithCoordinates: location
title:@"My Title"
subTitle:@"My Sub Title"];
/* И наконец, добавляем аннотацию на карту. */
[self.myMapView addAnnotation: annotation];
@end
На рис. 9.2 показан вывод данной программы в симуляторе iPhone.
Рис. 9.2. Интегрированный в систему стандартный маркер, отображенный на карте
9.5. Отображение разноцветных маркеров в картографическом виде
Постановка задачиПо умолчанию маркеры-индикаторы, которыми отмечаются точки на карте, – красного цвета. Необходимо отображать маркеры различных цветов, а не только стандартного красного.
РешениеВозвращайте вашему картографическому виду экземпляры MKPinAnnotationView. Это делается с помощью метода делегата mapView: viewForAnnotation:.
Каждая аннотация, добавляемая к экземпляру MKMapView, соответствует конкретному виду, который отображается поверх картографического вида. Такие всплывающие виды называются аннотирующими (Annotation Views).
Аннотирующий вид – это объект типа MKAnnotationView, он является подклассом от UIView. Если объект делегата картографического вида реализует метод делегата mapView: viewForAnnotation:, то объект делегата должен будет возвращать экземпляры класса MKAnnotationView, чтобы отображать (а при необходимости – настраивать) аннотирующие виды, которые выводятся поверх картографического вида.
ОбсуждениеЧтобы обеспечить в нашей программе возможность настройки цвета меток (цвет будем выбирать из стандартной палитры, предусмотренной для меток в SDK), которые ставятся на картографическом виде для представления аннотаций, нам понадобится возвращать в методе делегата mapView: viewForAnnotation: не экземпляр класса MKAnnotationView, а экземпляр класса MKPinAnnotationView. Не забывайте, что класс MKPinAnnotationView является подклассом MKAnnotationView.
– (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation{
MKAnnotationView *result = nil;
if ([annotation isKindOfClass: [MyAnnotation class]] == NO){
return result;
}
if ([mapView isEqual: self.myMapView] == NO){
/* Мы собираемся обработать это событие только для того Map View,
который создали ранее. */
return result;
}
/* Сначала приводим тип той аннотации, для которой этот Map View
запустил данное сообщение делегата. */
MyAnnotation *senderAnnotation = (MyAnnotation *)annotation;
/* С помощью метода класса, определенного нами в собственном
классе аннотаций, мы попытаемся сделать многоразовый идентификатор
для того маркера, который сейчас создаем. */
NSString *pinReusableIdentifier =
[MyAnnotation
reusableIdentifierforPinColor: senderAnnotation.pinColor];
/* Пользуясь идентификатором, полученным ранее, попытаемся
повторно применить маркер в отправляющем Map View. */
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)
[mapView
dequeueReusableAnnotationViewWithIdentifier: pinReusableIdentifier];
if (annotationView == nil){
/* Если нам не удастся повторно использовать имеющийся маркер,
создадим новый. */
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation: senderAnnotation
reuseIdentifier: pinReusableIdentifier];
/* Убеждаемся, что видны выноски поверх каждого маркера в случае,
если мы присвоили каждому маркеру заголовок и/или подзаголовок. */
[annotationView setCanShowCallout: YES];
}
/* Теперь (независимо от того, использовали мы многоразовый маркер
или создали новый) убеждаемся, что цвет маркера совпадает с цветом
аннотации. */
annotationView.pinColor = senderAnnotation.pinColor;
result = annotationView;
return result;
}
При многократном использовании аннотирующего вида ему присваивается идентификатор (строка NSString). Определяя, маркер какого типа вы хотели бы отобразить на карте, и задавая уникальный идентификатор для маркера каждого типа (например, к одному типу могут относиться красные маркеры, а к другому – синие), следует многократно использовать маркеры нужного типа, применяя метод экземпляра dequeueReusableAnnotationViewWithIdentifier:, относящийся к классу MKMapView. Это показано в следующем коде.
Мы запрограммировали механизм получения уникальных идентификаторов каждого маркера в собственном классе MyAnnotation. Вот. h-файл класса MyAnnotation:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
/* Это стандартные цвета меток, присутствующие в SDK. Мы задаем уникальные
идентификаторы для каждого маркера в соответствии с его цветом, чтобы
позже можно было снова использовать созданные ранее маркеры в связи
с тем же цветом, для которого они создавались. */
extern NSString *const kReusablePinRed;
extern NSString *const kReusablePinGreen;
extern NSString *const kReusablePinPurple;
@interface MyAnnotation: NSObject <MKAnnotation>
/* unsafe_unretained, так как это не объект. Этот шаг можно пропустить
и оставить принятие этого решения компилятору. weak или strong
не сработают, так как это не объект. */
@property (nonatomic, unsafe_unretained, readonly)
CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
/* unsafe_unretained по той же причине, что и для свойства coordinate */
@property (nonatomic, unsafe_unretained) MKPinAnnotationColor pinColor;
– (instancetype)initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString*)paramTitle
subTitle:(NSString*)paramSubTitle;
+ (NSString *) reusableIdentifierforPinColor
:(MKPinAnnotationColor)paramColor;
@end
Аннотация не то же самое, что аннотирующий вид. Аннотация – это место, которое вы хотите указать на карте, а аннотирующий вид – это визуальное представление, в котором эта аннотация всплывает над картой (то есть вид). Класс MyAnnotation соответствует аннотации, а не аннотирующему виду. Когда мы создаем аннотацию путем инстанцирования класса MyAnnotation, мы можем присвоить ей цвет, задействовав определенное и реализованное нами же свойство pinColor. Когда картографический вид должен будет отобразить аннотацию, картографический вид вызовет метод делегата mapView: viewForAnnotation: и запросит у этого делегата аннотирующий вид. В параметре forAnnotation данного метода сообщается аннотация, которую необходимо отобразить. Получая ссылку на аннотацию, мы можем привести тип аннотации к экземпляру MyAnnotation, получить ее свойство pinColor и, основываясь на этих данных, создать экземпляр класса MKPinAnnotationView. У этого экземпляра будет информация о заданном цвете маркера, которую мы вернем картографическому виду.
Вот. m-файл MyAnnotation:
#import «MyAnnotation.h»
NSString *const kReusablePinRed = @"Red";
NSString *const kReusablePinGreen = @"Green";
NSString *const kReusablePinPurple = @"Purple";
@implementation MyAnnotation
+ (NSString *) reusableIdentifierforPinColor
:(MKPinAnnotationColor)paramColor{
NSString *result = nil;
switch (paramColor){
case MKPinAnnotationColorRed:{
result = REUSABLE_PIN_RED;
break;
}
case MKPinAnnotationColorGreen:{
result = REUSABLE_PIN_GREEN;
break;
}
case MKPinAnnotationColorPurple:{
result = REUSABLE_PIN_PURPLE;
break;
}
}
return result;
}
– (instancetype)initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
title:(NSString*)paramTitle
subTitle:(NSString*)paramSubTitle{
self = [super init];
if (self!= nil){
_coordinate = paramCoordinates;
_title = paramTitle;
_subtitle = paramSubTitle;
_pinColor = MKPinAnnotationColorGreen;
}
return self;
}
@end
Выполнив реализацию класса MyAnnotation, его нужно задействовать в приложении (в данном примере мы воспользуемся контроллером вида). Вот верхняя часть файла реализации контроллера вида:
#import «ViewController.h»
#import «MyAnnotation.h»
#import <MapKit/MapKit.h>
@interface ViewController () <MKMapViewDelegate>
@property (nonatomic, strong) MKMapView *myMapView;
@end
@implementation ViewControllerРеализация в файле. m будет такой:
– (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation{
MKAnnotationView *result = nil;
if ([annotation isKindOfClass: [MyAnnotation class]] == NO){
return result;
}
if ([mapView isEqual: self.myMapView] == NO){
/* Мы собираемся обработать это событие только для того Map View,
который мы создали ранее. */
return result;
}
/* Сначала приводим тип той аннотации, для которой этот Map View
запустил данное сообщение делегата. */
MyAnnotation *senderAnnotation = (MyAnnotation *)annotation;
/* С помощью метода класса, определенного в нашем собственном
классе аннотаций, попытаемся сделать многоразовый идентификатор
для того маркера, который сейчас создаем. */
NSString *pinReusableIdentifier =
[MyAnnotation
reusableIdentifierforPinColor: senderAnnotation.pinColor];
/* Пользуясь идентификатором, полученным ранее, попытаемся
повторно применить маркер в отправляющем Map View. */
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)
[mapView
dequeueReusableAnnotationViewWithIdentifier: pinReusableIdentifier];
if (annotationView == nil){
/* Если нам не удастся повторно использовать имеющийся маркер,
создадим новый. */
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation: senderAnnotation
reuseIdentifier: pinReusableIdentifier];
/* Убеждаемся, что видны выноски поверх каждого маркера в случае,
если мы присвоили каждому маркеру заголовок и/или подзаголовок. */
[annotationView setCanShowCallout: YES];
}
/* Теперь (независимо от того, использовали мы многоразовый маркер
или создали новый) убеждаемся, что цвет маркера совпадает с цветом
аннотации. */
annotationView.pinColor = senderAnnotation.pinColor;
result = annotationView;
return result;
}
– (void)viewDidLoad {
[super viewDidLoad];
/* Создаем карту такого же размера, как и наш вид. */
self.myMapView = [[MKMapView alloc]
initWithFrame: self.view.bounds];
self.myMapView.delegate = self;
/* Задаем для карты тип Standard. */
self.myMapView.mapType = MKMapTypeStandard;
self.myMapView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
/* Добавляем ее к нашему виду. */
[self.view addSubview: self.myMapView];
/* Это просто один образец местоположения. */
CLLocationCoordinate2D location;
location.latitude = 50.8219 16929 07181;
location.longitude = -0.13 81176 71012 87842;
/* Создаем аннотацию, используя информацию о местоположении. */
MyAnnotation *annotation =
[[MyAnnotation alloc] initWithCoordinates: location
title:@"My Title"
subTitle:@"My Sub Title"];
annotation.pinColor = MKPinAnnotationColorPurple;
/* И наконец, добавляем аннотацию на карту. */
[self.myMapView addAnnotation: annotation];
}
Результат проделанной работы показан на рис. 9.3.
Рис. 9.3. Маркер альтернативного цвета, отображенный в картографическом виде
9.6. Отображение пользовательских маркеров в картографическом виде
Постановка задачиВместо стандартных маркеров, присутствующих в iOS SDK, требуется использовать на карте в таком качестве наши собственные изображения.
РешениеЗагружаем произвольное изображение в экземпляр класса UIImage и присваиваем этот экземпляр свойству image экземпляра MKAnnotationView. В результате выбранное нами изображение возвращается карте в виде маркера:
– (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation{
MKAnnotationView *result = nil;
if ([annotation isKindOfClass: [MyAnnotation class]] == NO){
return result;
}
}
if ([mapView isEqual: self.myMapView] == NO){
/* Мы собираемся обработать это событие только для того Map View,
который создали ранее. */
return result;
}
/* Сначала приводим тип той аннотации, для которой этот Map View
запустил данное сообщение делегата. */
MyAnnotation *senderAnnotation = (MyAnnotation *)annotation;
/* С помощью метода класса, определенного в нашем собственном
классе аннотаций, попытаемся сделать многоразовый идентификатор
для того маркера, который сейчас создаем. */
NSString *pinReusableIdentifier =
[MyAnnotation
reusableIdentifierforPinColor: senderAnnotation.pinColor];
/* Пользуясь идентификатором, полученным ранее, попытаемся повторно
применить маркер в отправляющем Map View. */
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)
[mapView
dequeueReusableAnnotationViewWithIdentifier:
pinReusableIdentifier];
if (annotationView == nil){
/* Если нам не удастся повторно использовать имеющийся маркер,
создадим новый. */
annotationView =
[[MKPinAnnotationView alloc] initWithAnnotation: senderAnnotation
reuseIdentifier: pinReusableIdentifier];
/* Убеждаемся, что видны выноски поверх каждого маркера в случае,
если мы присвоили каждому маркеру заголовок и/или подзаголовок. */
annotationView.canShowCallout = YES;
}
UIImage *pinImage = [UIImage imageNamed:@"BluePin.png"];
if (pinImage!= nil){
annotationView.image = pinImage;
}
result = annotationView;
return result;
}
В данном коде отображаем картинку под названием BluePin.png (в пакете нашего приложения) для любого маркера, который ставится на карте. Определение реализации класса MyAnnotation приводится в разделе 9.5.
ОбсуждениеОбъект делегата, относящийся к классу MKMapView, должен соответствовать протоколу MKMapViewDelegate и реализовывать метод mapView: viewForAnnotation:. Возвращаемое значение этого метода является экземпляром класса MKAnnotationView. Любой объект, являющийся подклассом вышеупомянутого класса, по умолчанию наследует свойство image. Если присвоить этому свойству такое значение, то мы заменим стандартное значение, предоставляемое во фреймворке Map Kit. Результат показан на рис. 9.4.
Рис. 9.4. Наше собственное изображение, показанное в картографическом виде
См. такжеРаздел 9.5.
9.7. Преобразование обычных адресов в данные широты и долготы
Постановка задачиИмеется адрес определенного места, необходимо найти его географические координаты (широту и долготу).
РешениеВоспользуйтесь методом geocodeAddressString: completionHandler: из класса CLGeocoder.
ОбсуждениеОбратное геокодирование (Reverse Geocoding) – это процесс получения обычного адреса (то есть страны, города и т. д.) на базе известного пространственного расположения (координат широты и долготы). В свою очередь, геокодирование – это процесс нахождения пространственного расположения в сетке координат на основе известного адреса. Функции геокодирования и обратного геокодирования заключены в классе CLGeocoder фреймворка Core Location.
Пространственное местоположение геокодируется путем передачи адреса в формате NSString методу geocodeAddressString: completionHandler:, относящемуся к классу CLGeocoder. Параметр completionHandler этого метода принимает блоковый объект, не возвращающий никакого значения и имеющий два параметра:
• массив меток (типа NSArray). Метками будут обозначены те точки на карте, которые соответствуют критериям поискового запроса;
• ошибку (типа NSError), которая будет преобразована в код ошибки, если геокодирование не удастся.
Итак, сначала объявим свойство типа CLGeocoder:
#import «ViewController.h»
#import <CoreLocation/CoreLocation.h>
@interface ViewController ()
@property (nonatomic, strong) CLGeocoder *myGeocoder;
@end
@implementation ViewController
Идем дальше. Реализуем код для геокодирования адреса:
– (void)viewDidLoad{
[super viewDidLoad];
/* У нас есть адрес. */
NSString *oreillyAddress =
@"1005 Gravenstein Highway North, Sebastopol, CA 95472, USA";
self.myGeocoder = [[CLGeocoder alloc] init];
[self.myGeocoder
geocodeAddressString: oreillyAddress
completionHandler: ^(NSArray *placemarks, NSError *error) {
if ([placemarks count] > 0 &&
error == nil){
NSLog(@"Found %lu placemark(s).", (unsigned long)[placemarks count]);
CLPlacemark *firstPlacemark = [placemarks objectAtIndex:0];
NSLog(@"Longitude = %f",
firstPlacemark.location.coordinate.longitude);
NSLog(@"Latitude = %f", firstPlacemark.location.coordinate.latitude);
}
else if ([placemarks count] == 0 &&
error == nil){
NSLog(@"Found no placemarks.");
}
else if (error!= nil){
NSLog(@"An error occurred = %@", error);
}
}];
}
Как только программа будет запущена (даже в симуляторе), в окне консоли появятся следующие значения (при наличии активного сетевого соединения):
Found 1 placemark(s).
Longitude = -122.8 41135
Latitude = 38.4 10373
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.