Dec
26

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

Сегодня у нас статья хабрапользователя slatvick о разработке iPhone-приложений, отображающих карты.

Введение

Я уже устал от ограниченности встроенного контрола карт, даже скорее от прикручивания кастылей. Постой пример: 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. Установите контрол шаг за шагом по этому гайду

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

routeme-shot

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

Открываем хедер UIViewController (мой называется «RouteMeSourceSelectionViewController.h») и:

  1. #import «RMMapView.h»
  2. добавляем протокол «RMMapViewDelegate»
  3. создаем IBOutlet RMMapView *mapView;

routeme-shot-2

Переопределяем метод “- (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

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

routeme-shot-3

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

routeme-shot-5

Все это было очень легко. Давайте дадим пользователю возможность выбирать источник карты. Для этого разместим контрол 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»

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

routeme-last-design

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

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

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

Еще интересные посты о программировании для мобильных устройств:

No Comments

Make A Comment

No comments yet.

Comments RSS Feed   TrackBack URL

Leave a comment

Please leave these two fields as-is:

top