Как сделать нестандартное всплывающее окошко при клике на метку в картах iOS

в 10:13, , рубрики: всплывающее окно, карта, метка, разработка под iOS, метки: , ,

image
Этот пост адресован в первую очередь начинающим (и не очень) разработчикам на iOS.
Часто в приложениях требуется разместить карту с метками на какие-то места. Стандартными средствами можно изменить картинку метки, во всплывающем окошке при щелчке на метку можно изменить заголовок, подзаголовок, картинку, а также добавлять справа кнопку или еще что-то.
При этом, все эти элементы могут быть только стандартного размера и будут выглядеть примерно так, как на картинке справа.

Как сделать нестандартное всплывающее окошко при клике на метку в картах iOSНо что, если требуется создать нестандартное всплывающее окошко, в которое можно будет разместить все, что угодно, (ну почти, все, что угодно) как на картинке слева?

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

Те, кто уже сталкивались с такой задачей, наверняка знают о https://github.com/nfarina/calloutview. На основе этого примера и построено мое решение. Недочет в решении nfarina в том, что его пример показывает, как использовать собственное всплывающее окошко для одной метки. А как выводить множество меток, в примере не рассматривается. Также он зачем-то вывел 2 карты и разместил по одной метке на каждой и написал все это в одном месте. Это может немного запутать. Ну и нет описания на русском.

Пошаговая инструкция для использования

Для начала, к проекту надо будет добавить два стандартных фреймворка – MapKit и QuartzCore. (Второй необязательно, но в моем примере он используется)

Как сделать нестандартное всплывающее окошко при клике на метку в картах iOS

Далее, в проект следует добавить 2 файла – SMCalloutView.h и SMCalloutView.m. Этот класс и будет формировать всплывыающие окошки. Его я позаимствовал у nfarina. В нем вам, вероятнее всего, ничего править не придется, поэтому его рассматривать не будем.

Основное внимание будет уделено контроллеру, выводящему карту и метки на ней.

Как и в примере nfarina, в файле, содержащем контроллер, который выводит карту, также будет 2 класса MapAnnotation и CustomPinAnnotationView. Может это и не очень правильно, но в данном случае мне это показалось более изящным и простым решением, т.к. эти классы неразрывно связаны с контроллером и вне его использоваться навряд ли будут.

В файле контроллера следует импортировать библиотеку MapKit/MapKit.h и файл SMCalloutView.h. Мой контроллер будет наследоваться от UIViewController и соответствовать протоколам MKMapViewDelegate и SMCalloutViewDelegate. Также класс должен содержать переменную, которая должна хранить всю информацию о выводимых объектах. В моем случае это будет stocks типа NSArray. В вашем будет что-то другое.

Полный код моего хедер файла контроллера NewMapViewController.h:

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import "SMCalloutView.h"

@interface NewMapViewController : UIViewController <MKMapViewDelegate, SMCalloutViewDelegate>
@property (nonatomic, strong) NSArray *stocks;
@end

@interface MapAnnotation : NSObject <MKAnnotation>
@property (nonatomic, copy) NSString *title, *subtitle;
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property int idAnn;
@end

@interface CustomPinAnnotationView : MKPinAnnotationView
@property (strong, nonatomic) SMCalloutView *calloutView;
@property int idAnn;
@end

Переменная idAnn типа int будет хранить id метки (или какого-нибудь другого вашего объекта), всплывающее окошко которой показывается в данный момент. Т.е. щелкнули по метке, всплыло окошко, idAnn поменялось. Рассмотрим имплементацию контроллера более подробно.

В моем случае в файле NewMapViewController.m импортируется библиотека QuartzCore и объявляются следующие локальные переменные для класса:

#import "NewMapViewController.h"
#import <QuartzCore/QuartzCore.h>

@implementation NewMapViewController{
    SMCalloutView *calloutView;
    MKMapView *mapView;
    NSMutableArray *pins;
    int idAnn;
}

calloutView – всплывающее окошко, mapView – карта, pins – массив выводимых меток (не путать со stocks), idAnn – id метки, у которой показывается всплывающее окошко.

В моем случае в файле init инициализируется массив stocks, который будет содержать информацию о выводимых метках (картинка, заголовок, цена, координаты).

- (id)init
{
    self = [super init];
    if (self)
    {
        pins = [NSMutableArray array];
        self.stocks = @[
            @{@"img" : @"1.jpg", @"title" : @"Бургеры, салаты, курочка и многое другое в сети ресторанов быстрого питания «DAL's Burger» и «Gold'n'Brown». Скидка 50%", @"price" : @"50% за 99 тг.", @"lat" : @"43.20138", @"lng" : @"76.90597"},
            //...
            @{@"img" : @"6.jpg", @"title" : @"Две пиццы по цене одной в итальянском ресторане «Del Papa». Скидка 50%", @"price" : @"3000 тг. - 50% = 1499 тг.", @"lat" : @"43.25417", @"lng" : @"76.94035"}
                       ];
    }
    return self;
}

В вашем случае эту информацию вы можете получить из любого другого места (json, xml, другие классы и т.д.).

Мой viewDidLoad содержит всего 2 метода:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self setMap];
    [self drawMarkers];
}

setMap – инициализирует карту и показывает нужный участок. drawMarkers – рисует метки на карте. Вам надо будет изменить эти методы под свои нужды. (Далее не буду приводить весь код. По необходимости вы всегда сможете скачать готовый пример внизу по ссылке на гитхабе и посмотреть)

В методе setMap также указывается, что у всех всплывающих окошек будет добавляться кнопочка справа, по нажатию на которую должно произойти событие, связанное именно с этой меткой:

  //...
   UIButton *bottomDisclosure = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
   [bottomDisclosure addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(goToMarker)]];
   calloutView.rightAccessoryView = bottomDisclosure;
}

Метод goToMarker вы должны изменить под свои нужды. В моем случае просто всплывает UIAlertView с информацией о нажатой метке:

- (void)goToMarker
{
    // Change this for your case
    NSDictionary *item = [self.stocks objectAtIndex:idAnn];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Ура!"
                                                    message:[item objectForKey:@"title"]
                                                   delegate:self
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil];
    [alert show];
}

Для изменения дизайна своего всплывающего окошка вам надо будет изменить функцию popupMapCalloutView

- (void)popupMapCalloutView {
    UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 260, 88)];
    NSDictionary *item = [self.stocks objectAtIndex:idAnn];
    
    UIImageView *photo = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 88.0, 88.0)];
    photo.image = [UIImage imageNamed:[item objectForKey:@"img"]];
    photo.contentMode = UIViewContentModeScaleAspectFit;
    photo.layer.cornerRadius = 15.0;
    photo.layer.masksToBounds = YES;
    [customView addSubview:photo];
    //...
}

В моем случае данные будут браться из массива stocks. Какой элемент показывается, хранит переменная idAnn.

Заключение

Конечно, я не считаю, что сделал что-то гениальное и сильно отличающееся от примера nfarina, но все же, мое руководство предназначено для русскоговорящих (а ведь на русском языке очень мало инфы о разработке под iOS). И мой пример расширяет функционал первоначального примера. (Также я не рассмотрел некоторые отличия моих методов контроллера от первоначальных методов nfarina, которые вам править не придется. А может и придется)

Прошу разработчиков указывать на мои ошибки (куда без них?), а также поделиться другими решениями данной задачи (если таковые имеются в сети). И все обсудить в комментариях.

Мой пример вы можете скачать на гитхабе по этой ссылке – https://github.com/IbrahimKZ/CustomCalloutViews

Автор: IbrahimKZ

Источник

Поделиться

* - обязательные к заполнению поля