Pull to refresh

Route Me — альтернатива встроенному Google Maps контролу из iPhone SDK 3.0+

Reading time 5 min
Views 3.5K
image

Я уже устал от ограниченности встроенного контрола карт, даже скорее от прикручивания костылей. Постой пример: Google Maps app маршрут показывать умеет, a контрол не умеет. Приходится рисовать своими силами поверх карты.

Сейчас передо мной стоит конкретная задача: надо добавить отображение пройденного маршрута и его экспорт (share) в мое скромное приложение GPS Speed, которое, кстати, уже который день висит в Топ 30 американского App Store в разделе Navigation. Причина для меня остается загадкой, потому как приложение среднее и уникальностью не блещет.

Возвращаюсь к проблеме. Нужный функционал уже реализованПочти рабочие костыли для Google Maps уже сделаны, но это хороший шанс пролить свет на альтернативы Google Maps для iPhone SDK.

Мат часть


Еще когда не было официального контрола карт, я работал с такими «заменителями»:

Route Me:
  1. BSD лицензия (решать вам хорошо это или плохо)
  2. поддержка многих источников карт: OpenStreetMap, Microsoft Virtual Earth, Cloud Made, Yahoo Maps и другие менеее популярные
  3. сформированное активное сообщество разработчиков — Google Group

Cloud Made:
  1. часть большого проекта и имеет официальную команду разработки (воспользуюсь случаем и передам привет Диме ;), потому потенциально имеет большую интеграцию с картами Cloud Made
  2. имеет много тем (цветовые наборы) для карт
  3. использовал весной, было еще сырое

Хочу начать цикл статей с чего то простого, например, встроить карты в приложение и дать пользователю возможность выбирать источник карт. Я предпочитаю Route Me, потому как, кроме всего остального, он может показыват и Cloud Made.

Подготовка

  1. Откройте xCode и создайте новый проект. Я выбрал View-Based Application и назвал его RouteMeSourceSelection
  2. Загрузите последнюю версию Route Me
  3. Установите контрол шаг за шагом по этому гайду

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

image

Добавляем контрол (код)


Открываем хедер UIViewController (мой называется «RouteMeSourceSelectionViewController.h») и:
  1. #import «RMMapView.h»
  2. добавляем протокол «RMMapViewDelegate»
  3. создаем IBOutlet RMMapView *mapView;

image

Переопределяем метод "- (void)viewDidLoad":
- (void)viewDidLoad {
[super viewDidLoad];

[RMMapView class]; // важно! без этого карта не покажется. стеснительная.
[mapView setDelegate:self];
}


Добавляем контрол (дизайн)


  1. Открываем .xib file в Interface Builder (IB)
  2. Добавляем UIView (subview) в уже существующий и определяем его, как RMMapView
  3. Соединяем IBOutlet из нашего UIViewController с только что созданным RMMapView

Понимаю, что трудно воспринимать такой текст. Короче, должно выглядеть вот так:
image

Теперь, запустив приложение, вы должны увидеть карту с источником по умолчанию — Open Street Maps. Я вижу такое, надеюсь вы тоже:
image

Все это было очень легко. Давайте дадим пользователю возможность выбирать источник карты. Для этого разместим контрол UIPickerView и свяжим его с нашим UIViewController.

UIPickerView для выбора источника карты (код)


Октрываем хедер контролера и добавляем два протокола UIPickerViewDelegate и UIPickerViewDataSource, и несколько IBOutlet:
@interface RouteMeSourceSelectionViewController : UIViewController
<RMMapViewDelegate,
UIPickerViewDelegate, UIPickerViewDataSource>
{
IBOutlet RMMapView *mapView;
IBOutlet UIPickerView *mapSourcePicker;
IBOutlet UIBarButtonItem *mapSettingsBarButton;
}

- (IBAction) showMapsSettings;

@end


Теперь реализуем несколько обязательных методов для UIPickerView. Это просто, но будет полезным для тех, кто этого еще не делал:

static NSArray *titles = nil;

- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
return [titles objectAtIndex:row];
}

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
if (!titles)
{
titles = [[NSArray alloc] initWithObjects:
@"Open Street Maps",
@"Yahoo Map",
@"Virtual Earth Aerial",
@"Virtual Earth Hybrid",
@"Virtual Earth Road",
@"Cloud Made Map",
nil];
}

return [titles count];
}

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}


Создадим метод showMapsSettings для того, что бы показывать и прятать UIPickerView, и еще для парочки UI трюков:

- (IBAction) showMapsSettings
{
BOOL toShow = [mapSourcePicker isHidden];

if (toShow)
{
[mapSettingsBarButton setStyle:UIBarButtonItemStyleDone];
}
else // hidding
{
[mapSettingsBarButton setStyle:UIBarButtonItemStyleBordered];
[self setMapSourceWithNumber:[mapSourcePicker selectedRowInComponent:0]];
}

[mapSourcePicker setHidden:![mapSourcePicker isHidden]];
[mapView setUserInteractionEnabled:[mapSourcePicker isHidden]];
}


А теперь самое главное — метод для изменения источника карты. Если были внимательны, то видели, что он вызывается в «showMapsSettings». Тут есть небольшой трюк: карта не обновляется автоматически при смене источника, потому приходится сдвигать и возвращать в прежнее положение. Это незаметно для пользователя. В самом конце сохраняем выбранный номер источника в общие настройки приложения.

#import "RMVirtualEarthSource.h"
#import "RMYahooMapSource.h"
#import "RMCloudMadeMapSource.h"
#import "RMOpenStreetMapsSource.h"

#define CONST_MAP_KEY_bing @""
#define CONST_MAP_KEY_cloud @""

- (void) setMapSourceWithNumber:(int)number
{
if (mapSourceNumber == number)
return;

switch (number) {
case 0:
mapView.contents.tileSource = [[RMOpenStreetMapsSource alloc] init];
break;
case 1:
mapView.contents.tileSource = [[RMYahooMapSource alloc] init];
break;

case 2:
mapView.contents.tileSource = [[RMVirtualEarthSource alloc] initWithAerialThemeUsingAccessKey:CONST_MAP_KEY_bing];
break;
case 3:
mapView.contents.tileSource = [[RMVirtualEarthSource alloc] initWithHybridThemeUsingAccessKey:CONST_MAP_KEY_bing];
break;
case 4:
mapView.contents.tileSource = [[RMVirtualEarthSource alloc] initWithRoadThemeUsingAccessKey:CONST_MAP_KEY_bing];
break;

case 5:
mapView.contents.tileSource = [[RMCloudMadeMapSource alloc] initWithAccessKey:CONST_MAP_KEY_cloud styleNumber:1];
break;

default:
return;
break;
}

// this trick refreshs maps with new source
[mapView moveBy:CGSizeMake(640,960)];
[mapView moveBy:CGSizeMake(-640,-960)];

mapSourceNumber = number;
// remember user choice between runnings
[[NSUserDefaults standardUserDefaults] setInteger:mapSourceNumber forKey:@"mapSourceNumber"];
}


Для того, что бы заработали карты Cloud Made и Virtual Earth (Bing) надо получить «developer API key»:


Последнее, что я бы хотел добавить это запоминание выбора пользователя между запусками нашей программы. Модифицируем метод «viewDidLoad»:

- (void)viewDidLoad {
[super viewDidLoad];

[RMMapView class];
[mapView setDelegate:self];

int number = [[NSUserDefaults standardUserDefaults] integerForKey:@"mapSourceNumber"];
[self setMapSourceWithNumber:number];
[mapSourcePicker selectRow:mapSourceNumber inComponent:0 animated:NO];
}


UIPickerView для выбора источника карты (дизайн)


Открываем IB и:
  1. добавляем UIToolbar с одной кнопкой UIBarButtonItem, соединенной с ее IBOutlet из контроллера
  2. соединяем событие нажатия на UIBarButtonItem с showMapsSettings IBAction
  3. добавляем UIPickerView над картой и делаем ее скрытой (hidden)
  4. соединяем UIPickerView с его IBOutlet и устанавливаем его delegate и dataSource, как «file's owner»

Опять же картинка заменит тысячу слов:
image

Если все сделано верно и ни я, ни вы ни чего не забыли, то вы увидите рабочее приложение:



Все что было показано используется в реальном приложении — Meeting Point, оно бесплатное, попробуйте.

Загрузить исходики можно тут
Английский вариант статьи из моего тех.блога

В комментариях предлагайте, что добавить и я буду расширять этот подробный гид:
  • Определения местоположения пользователя на карте и отображения на карте. Это сделаю точно, слишком просто.
  • Гео поиск. Еще не знаю, что это, но звучит круто
  • Предлагайте свое
Tags:
Hubs:
+17
Comments 24
Comments Comments 24

Articles