воскресенье, 3 января 2016 г.

38. Карты. MKMapView Part 2

Продолжаем разбираться в картах - MapKit.
Видео к этому уроку находится здесь.

Добавляем к всплывающему облачку над Pin справа кнопку информации. Для этого в метод

- (nullable MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation

добавляем

       UIButton *descriptionButton=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
        [descriptionButton addTarget:self action:@selector(actionDescription:) forControlEvents:UIControlEventTouchUpInside];

        pin.rightCalloutAccessoryView=descriptionButton;

Для того, чтобы узнать на каком Pin была нажата кнопка (для правильной обработки событий) применим ранее используемый метод с расширением категории UIView.
Создаем новую категорию для UIView с именем MKAnnotationView: 

File->New File->Objective C File 
выбираем из списка категория и вводим имя.

Для поиска владельца кнопки  создаем рекурсивную функцию в категории:

#import "UIView+MKAnnotationView.h"
#import <MapKit/MapKit.h>

@implementation UIView (MKAnnotationView)
- (MKAnnotationView*) superAnnotationView{
    
    if ([self isKindOfClass:[MKAnnotationView class]]) {
        return (MKAnnotationView*) self;
    }
    if (!self.subviews) {
        return nil;
    }
    return [self.superview superAnnotationView];

}


@end


Для получения адреса по координатам создаем проперти:

@property(nonatomic,strong) CLGeocoder *geoCoder;

и инициализируем его в viewDidLoad:

    self.geoCoder=[[CLGeocoder alloc] init];

Пишем обработчик кнопки:

-(void) actionDescription: (UIButton *) sender{

//определяем владельца кнопки с помощью нашей функции

    MKAnnotationView* annotationView = [sender superAnnotationView];
    
    if (!annotationView) {
        return;
    }
//сохраняем координаты аннотации    
    CLLocationCoordinate2D coordinate = annotationView.annotation.coordinate;

//используя широту и долготу инициализируем объект типа CLLocation
    CLLocation* location = [[CLLocation alloc] initWithLatitude:coordinate.latitude
                                                      longitude:coordinate.longitude];
//Если Геокодер что-то делает, то мы отменяем это действие    
    if ([self.geoCoder isGeocoding]) {
        [self.geoCoder cancelGeocode];
    }
//Делаем запрос к геокодеру по нашим координатам    
    [self.geoCoder
     reverseGeocodeLocation:location
     completionHandler:^(NSArray *placemarks, NSError *error) {
         
         NSString* message = nil;
//если произошла ошибка - выводим в сообщении         
         if (error) {
             
             message = [error localizedDescription];
             
         } else {
//если массив с адресом не пустой, выводим в сообщение             
             if ([placemarks count] > 0) {
                 
                 MKPlacemark* placeMark = [placemarks firstObject];
                 
                 message = [placeMark.addressDictionary description];
//если пустой массив, сообщаем об этом                 
             } else {
                 message = @"No Placemarks Found";
             }
         }
//Выводим сообщение с помощью AlerController (для этого создали функцию showAlertWithTitle)
         [self showAlertWithTitle:@"Location" andMessage:message];

    }];

}

UIAlertView используемый в данном уроке для вывода адреса или ошибки устарел Deprecated, поэтому используем UIAlertController:

-(void) showAlertWithTitle:(NSString*) title andMessage:(NSString*) message{
    
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:title
                                                                   message:message
                                                            preferredStyle:UIAlertControllerStyleAlert];
    
    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
                                                          handler:^(UIAlertAction * action) {}];
    
    [alert addAction:defaultAction];
    [self presentViewController:alert animated:YES completion:nil];
    

}


Добавляем еще одну кнопку в облачко Pin для отображения пути от текущей геопозиции до Pin.   В функцию
- (nullable MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation

добавляем

        UIButton *wayButton=[UIButton buttonWithType:UIButtonTypeContactAdd];
        [descriptionButton addTarget:self action:@selector(actionWayDirection:) forControlEvents:UIControlEventTouchUpInside];

        pin.leftCalloutAccessoryView=wayButton;

Добавляем проперти  MKDirections, чтобы переменная не уничтожилась раньше времени 

@property (nonatomic,strong) MKDirections *directions;


Добавляем обработчик этой кнопки

-(void) actionWayDirection: (UIButton *) sender{

//Ищем аннотацию - владельца нажатой кнопки с помощью нашей рекурсивной функции    

    MKAnnotationView* annotationView = [sender superAnnotationView];
    
    if (!annotationView) {
        return;
    }

//Если происходит вычисление пути, отменяем вычисление

    if ([self.directions isCalculating]) {
        [self.directions cancel];
    }

// Получаем координаты из аннотации    
    CLLocationCoordinate2D coordinate = annotationView.annotation.coordinate;
    
// Создаем запрос     
    MKDirectionsRequest *request=[[MKDirectionsRequest alloc]init];

//Заполняем запрос  из текущего местоположения
    request.source = [MKMapItem mapItemForCurrentLocation];

//Создаем MKPlacemark  из координат аннотации  
    MKPlacemark *placemark=[[MKPlacemark alloc]initWithCoordinate:coordinate addressDictionary:nil];

//Создаем MKMapItem из полученной placemark
    MKMapItem *destination = [[MKMapItem alloc] initWithPlacemark:placemark];

//Заполняем запрос полученным destination
    request.destination = destination;

//Тип транспорта на котором можно передвигаться - автомобиль
    request.transportType= MKDirectionsTransportTypeAutomobile;

//Для показа альтернативных путей 
    request.requestsAlternateRoutes=YES;
//Инициализируем нашу property  c помощью сформированного запроса 
    self.directions = [[MKDirections alloc] initWithRequest:request];

//Запускаем на вычисление - поиски маршрутов
    [self.directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse * _Nullable response, NSError * _Nullable error) {

//Если существует ошибка, выводим её
        if (error) {
            [self showAlertWithTitle:@"Error" andMessage:[error localizedDescription]];
        }
//Если направлений 0 - выводим сообщение об этом
        else if ([response.routes count]==0){
            [self showAlertWithTitle:@"Error" andMessage:@"No routes found"];
        }
        else{
//Удаляем старые нарисованные маршруты
            [self.mapView removeOverlays:[self.mapView overlays]];

//Формируем массив с маршрутами для прорисовки
            
            NSMutableArray *arrayRoute=[NSMutableArray array];
            for (MKRoute *route in response.routes) {
                [arrayRoute addObject:route.polyline];
            }
//Рисуем на карте наши маршруты            
            [self.mapView addOverlays:arrayRoute level:MKOverlayLevelAboveRoads];
            
        }
      
    }];

}


Исходный текст проекта находится здесь.

Домашнее задание к этому уроку:

По ходу выполнения домашнего задания завис на 5 пункте, т.к. картинки для Pin не отображались.
Оказывается с IOS 9  для класса MKPinAnnotationView  отображаются только стандартные картинки для Пина. Можно только менять цвета.
Пришлось для кастомных картинок использовать родительский класс MKAnnotationView  который не имеет свойств таких как анимированное падение и цвет пина.
Вот такая получилась функция для 5 задания:

- ( MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{

    if ([annotation isKindOfClass:[MKUserLocation class]]) {
        return nil;
    }
    static NSString *pinIdentifier=@"AnnotationStudent";
    MKAnnotationView *pin=(MKAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:pinIdentifier];

    if (!pin) {
        pin=[[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:pinIdentifier];
        pin.canShowCallout=YES;
        pin.draggable=YES;
    }
    else{
        pin.annotation=annotation;
    }

    if ([annotation isKindOfClass:[TAStudent class]]) {
        TAStudent *student=(TAStudent*) annotation;

        NSString *imageName=student.isMen?@"man_pin":@"woman_pin";
        UIImage *imagePin=[UIImage imageNamed:imageName];
        pin.image = imagePin;

    }
  
    return pin;

}


Выполнил все задания кроме супермена (нужно следующие уроки разбирать).

Исходник с домашним заданием здесь.





Ученик. 

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

2. Координату генерируйте так, установите центр например в вашем городе и просто генерируйте небольшие отрицательные либо положительные числа, чтобы рандомно получалась координата от центра в пределах установленного радиуса.

(Ну а если совсем не получается генерировать координату, то просто ставьте им заготовленные координаты - это не главное)

3. После того, как вы сгенерировали своих студентов, покажите их всех на карте, причем в титуле пусть будет Имя и Фамилия а в сабтитуле год рождения. Можете для каждого студента создать свою аннотацию, а можете студентов подписать на протокол аннотаций и добавить их на карту напрямую - как хотите :)

Студент.

4. Добавьте кнопочку, которая покажет всех студентов на экране.

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

Мастер

6. У каждого колаута (этого облачка над пином) сделайте кнопочку информации справа так, что когда я на нее нажимаю вылазит поповер, в котором в виде статической таблицы находится имя и фамилия студента, год рождения, пол и самое главное адрес.

7. В случае если это телефон, то вместо поповера контроллер должен вылазить модально.

Супермен

8. Создайте аннотацию для места встречи и показывайте его на карте новымой соответствующей картинкой

9. Место встречи можно перемещать по карте, а студентов нет

10. Когда место встречи бросается на карту, то вокруг него надо рисовать 3 круга, с радиусами 5 км, 10 км и 15 км (используйте оверлеи)

11. На какой-то полупрозрачной вьюхе в одном из углов вам надо показать сколько студентов попадают в какой круг. Суть такая, чем дальше студент живет, тем меньше вероятность что он придет на встречу. Расстояние от студента до места встречи рассчитывайте используя функцию для расчета расстояния между точками, поищите ее в фреймворке :)

12. Сделайте на навигейшине кнопочку, по нажатию на которую, от рандомных студентов до нее будут проложены маршруты (типо студенты идут на сходку), притом вероятности генератора разные, зависит от круга, в котором они находятся, если он близко, то 90%, а если далеко - то 10%

Комментариев нет:

Отправить комментарий