Route Me - an alternative to the built-in Google Maps control from iPhone SDK 3.0+
I'm already tired of the limitations of the built-in card control, even rather of screwing up crutches. A blank example: the Google Maps app knows how to show a route, but the control does not know how. I have to draw on my own on top of the map.
Now I have a specific task: I need to add the display of the route traveled and its export (share) to my modest GPS Speed application , which, by the way, has been hanging on the Top 30 of the American App Store in the Navigation section for a day . The reason for me remains a mystery, because the application is average and uniqueness does not shine.
Going back to the problem.
Mat part
Even when there was no official control of the cards, I worked with such "substitutes":
Route Me:
- BSD license (you decide whether it is good or bad)
- support for many map sources: OpenStreetMap, Microsoft Virtual Earth, Cloud Made, Yahoo Maps and other less popular
- formed active developer community - Google Group
Cloud Made:
- part of a large project and has an official development team (I will take this opportunity and say hello to Dima;), therefore it potentially has great integration with Cloud Made cards
- has many themes (color sets) for maps
- used in spring, it was still raw
I want to start a series of articles with something simple, for example, embed maps in an application and give the user the opportunity to choose a map source. I prefer Route Me, because, among other things, it can also show Cloud Made.
Training
- Open xCode and create a new project. I selected View-Based Application and named it RouteMeSourceSelection
- Download the latest version of Route Me
- Set control step by step on this guide
After that you should have a compiled project. If you have any problems, you can download the sources of this example and compare.
Add control (code)
Open the UIViewController header (mine is called “RouteMeSourceSelectionViewController.h”) and:
- #import "RMMapView.h"
- add the protocol "RMMapViewDelegate"
- create IBOutlet RMMapView * mapView;
Redefine the method "- (void) viewDidLoad":
- (void)viewDidLoad {
[super viewDidLoad];
[RMMapView class]; // важно! без этого карта не покажется. стеснительная.
[mapView setDelegate:self];
}
Add control (design)
- Open .xib file in Interface Builder (IB)
- Add a UIView (subview) to an existing one and define it as RMMapView
- Connect the IBOutlet from our UIViewController to the newly created RMMapView
I understand that it is difficult to perceive such a text. In short, it should look like this:
Now, having launched the application, you should see a map with the default source - Open Street Maps. I see this, I hope you too:
All this was very easy. Let's give the user the opportunity to choose a map source. To do this, place the UIPickerView control and associate it with our UIViewController.
UIPickerView for map source selection (code)
We open the controller header and add two protocols UIPickerViewDelegate and UIPickerViewDataSource, and several IBOutlet: Now we implement several mandatory methods for UIPickerView. It’s simple, but it will be useful for those who haven’t done it yet: Let's create the showMapsSettings method to show and hide the UIPickerView, and also for a couple of UI tricks: And now the most important thing is the method for changing the map source. If you were careful, you saw that it was called in "showMapsSettings". There is a small trick: the map does not update automatically when the source changes, so you have to shift it and return it to its previous position. This is invisible to the user. At the very end, save the selected source number in the general application settings.
@interface RouteMeSourceSelectionViewController : UIViewController
<RMMapViewDelegate,
UIPickerViewDelegate, UIPickerViewDataSource>
{
IBOutlet RMMapView *mapView;
IBOutlet UIPickerView *mapSourcePicker;
IBOutlet UIBarButtonItem *mapSettingsBarButton;
}
- (IBAction) showMapsSettings;
@end
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;
}
- (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]];
}
#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"];
}
In order for Cloud Made and Virtual Earth (Bing) cards to work, you need to get the "developer API key":
The last thing I would like to add is remembering the user's choice between the launches of our program. We modify the “viewDidLoad” method:
- (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 for map source selection (design)
Open IB and:
- add a UIToolbar with one UIBarButtonItem button connected to its IBOutlet from the controller
- connect the click event on UIBarButtonItem with showMapsSettings IBAction
- add a UIPickerView above the map and make it hidden
- connect the UIPickerView with its IBOutlet and set its delegate and dataSource as “file's owner”
Again, a picture will replace a thousand words:
If everything is done right and neither I, nor you have forgotten anything, then you will see a working application:
Everything that was shown is used in a real application - Meeting Point, it is free, try it .
You can download the source here.
English version of the article from my tech blog.
In the comments suggest what to add and I will expand this detailed guide:
- Determine the user's location on the map and display on the map. I’ll do it for sure, too simple.
- Geo search. I don’t know what it is yet, but it sounds cool
- Offer your