Я уже устал от ограниченности встроенного контрола карт, даже скорее от прикручивания костылей. Постой пример: Google Maps app маршрут показывать умеет, a контрол не умеет. Приходится рисовать своими силами поверх карты.
Сейчас передо мной стоит конкретная задача: надо добавить отображение пройденного маршрута и его экспорт (share) в мое скромное приложение GPS Speed, которое, кстати, уже который день висит в Топ 30 американского App Store в разделе Navigation. Причина для меня остается загадкой, потому как приложение среднее и уникальностью не блещет.
Возвращаюсь к проблеме.
Мат часть
Еще когда не было официального контрола карт, я работал с такими «заменителями»:
Route Me:
- BSD лицензия (решать вам хорошо это или плохо)
- поддержка многих источников карт: OpenStreetMap, Microsoft Virtual Earth, Cloud Made, Yahoo Maps и другие менеее популярные
- сформированное активное сообщество разработчиков — Google Group
Cloud Made:
- часть большого проекта и имеет официальную команду разработки (воспользуюсь случаем и передам привет Диме ;), потому потенциально имеет большую интеграцию с картами Cloud Made
- имеет много тем (цветовые наборы) для карт
- использовал весной, было еще сырое
Хочу начать цикл статей с чего то простого, например, встроить карты в приложение и дать пользователю возможность выбирать источник карт. Я предпочитаю Route Me, потому как, кроме всего остального, он может показыват и Cloud Made.
Подготовка
- Откройте xCode и создайте новый проект. Я выбрал View-Based Application и назвал его RouteMeSourceSelection
- Загрузите последнюю версию Route Me
- Установите контрол шаг за шагом по этому гайду
После этого у вас должен быть компилируемый проект. Есле возникли какие то проблемы, то можете скачать исходники этого примера и сравнить.
Добавляем контрол (код)
Открываем хедер UIViewController (мой называется «RouteMeSourceSelectionViewController.h») и:
- #import «RMMapView.h»
- добавляем протокол «RMMapViewDelegate»
- создаем IBOutlet RMMapView *mapView;
Переопределяем метод "- (void)viewDidLoad":
- (void)viewDidLoad {
[super viewDidLoad];
[RMMapView class]; // важно! без этого карта не покажется. стеснительная.
[mapView setDelegate:self];
}
Добавляем контрол (дизайн)
- Открываем .xib file в Interface Builder (IB)
- Добавляем UIView (subview) в уже существующий и определяем его, как RMMapView
- Соединяем IBOutlet из нашего UIViewController с только что созданным RMMapView
Понимаю, что трудно воспринимать такой текст. Короче, должно выглядеть вот так:
Теперь, запустив приложение, вы должны увидеть карту с источником по умолчанию — Open Street Maps. Я вижу такое, надеюсь вы тоже:
Все это было очень легко. Давайте дадим пользователю возможность выбирать источник карты. Для этого разместим контрол 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 и:
- добавляем UIToolbar с одной кнопкой UIBarButtonItem, соединенной с ее IBOutlet из контроллера
- соединяем событие нажатия на UIBarButtonItem с showMapsSettings IBAction
- добавляем UIPickerView над картой и делаем ее скрытой (hidden)
- соединяем UIPickerView с его IBOutlet и устанавливаем его delegate и dataSource, как «file's owner»
Опять же картинка заменит тысячу слов:
Если все сделано верно и ни я, ни вы ни чего не забыли, то вы увидите рабочее приложение:
Все что было показано используется в реальном приложении — Meeting Point, оно бесплатное, попробуйте.
Загрузить исходики можно тут
Английский вариант статьи из моего тех.блога
В комментариях предлагайте, что добавить и я буду расширять этот подробный гид:
- Определения местоположения пользователя на карте и отображения на карте. Это сделаю точно, слишком просто.
- Гео поиск. Еще не знаю, что это, но звучит круто
- Предлагайте свое