Route in iOS applications
Having written several applications for the iPhone, in which the use of the map is one of the main use cases, I thought about the fact that a lot of objects on the map are very cool, informative and visual, but lack routes from one point to another.
Below I will share my experience in implementing this use-case.
My knowledge of the iOS SDK led me to 2 implementation possibilities:
The first method is complicated in that it is necessary to have a source of control points for the route, and then it is still not known how many of these points are needed for the lines to lie on all the bends of the roads, etc. Therefore, I think the second method will satisfy most of its capabilities.
Consider the implementation of a specific example. For work we need MapKit.framework.
We assume that we have an instance of UIViewController, which is located on the stack of UINavigationController and displays MKMapView. On the map, we will display UserLocation and the point where we need to get. In my practice, it often happens that our destination is already described by the address and we can only get the address of the user's current position.
Let's try in order.
Through the constructor, we transfer data that describes the destination: coordinates, name and address of the place. In the constructor, create an annotation for this place and a button “Route”, which will be displayed in the UINavigationBar and will become available to the user after the program finds the address of its current location.
Suppose that our controller is initiated without an xib file and self.view will point to an MKMapView instance. Once the map has determined the coordinates of the user, we will use MKReverseGeocoder to get the address by these coordinates (MKReverseGeocoder requires use according to certain rules, but I will not dwell on this here in detail). After we received the address, save it in the _selfAddress variable and make the “Route” button available to the user. After you click the "March" button, it remains to make a request at maps.google.com/maps ? daddr = & saddr =, where daddr and saddr are the end address and route start address, respectively. If the URL was composed correctly, the “Maps” application opens, in which the route is built.
Apple Url Scheme
______________________
Below I will share my experience in implementing this use-case.
Two visions
My knowledge of the iOS SDK led me to 2 implementation possibilities:
- Complicated. Using MKOverlayView.
- Simpler. Using the Maps application, installed by default.
The first method is complicated in that it is necessary to have a source of control points for the route, and then it is still not known how many of these points are needed for the lines to lie on all the bends of the roads, etc. Therefore, I think the second method will satisfy most of its capabilities.
Implementation
Consider the implementation of a specific example. For work we need MapKit.framework.
We assume that we have an instance of UIViewController, which is located on the stack of UINavigationController and displays MKMapView. On the map, we will display UserLocation and the point where we need to get. In my practice, it often happens that our destination is already described by the address and we can only get the address of the user's current position.
Let's try in order.
Through the constructor, we transfer data that describes the destination: coordinates, name and address of the place. In the constructor, create an annotation for this place and a button “Route”, which will be displayed in the UINavigationBar and will become available to the user after the program finds the address of its current location.
@interface RouteController : UIViewController {
MKPointAnnotation * _pointAnnotaion;
NSString * _selfAddress;
}
- (id)initWithPlaceCoordinate:(CLLocationCoordinate2D)coorinate
placeName:(NSString *)name
placeAddress:(NSString *)address;
@end
@implementation RouteController
- (id)initWithPlaceCoordinate:(CLLocationCoordinate2D)coorinate placeName:(NSString *)name placeAddress:(NSString *)address {
self = [super init];
if (self) {
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Маршрут"
style:UIBarButtonItemStyleBordered
target:self
action:@selector(routeAction)]
autorelease];
self.navigationItem.rightBarButtonItem.enabled = NO;
_pointAnnotaion = [MKPointAnnotation new];
_pointAnnotaion.title = name;
_pointAnnotaion.subtitle = address;
_pointAnnotaion.coordinate = coorinate;
}
return self;
}
- (void)dealloc {
[_pointAnnotaion release];
[_selfAddress release];
[super dealloc];
}
Suppose that our controller is initiated without an xib file and self.view will point to an MKMapView instance. Once the map has determined the coordinates of the user, we will use MKReverseGeocoder to get the address by these coordinates (MKReverseGeocoder requires use according to certain rules, but I will not dwell on this here in detail). After we received the address, save it in the _selfAddress variable and make the “Route” button available to the user. After you click the "March" button, it remains to make a request at maps.google.com/maps ? daddr = & saddr =, where daddr and saddr are the end address and route start address, respectively. If the URL was composed correctly, the “Maps” application opens, in which the route is built.
- (void)loadView {
MKMapView * mapView = [[[MKMapView alloc] initWithFrame:[UIScreen mainScreen].bounds] autorelease];
mapView.showsUserLocation = YES;
mapView.delegate = self;
[mapView addAnnotation:_pointAnnotaion];
self.view = mapView;
}
#pragma mark - Map view delegate
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
MKReverseGeocoder * geocoder = [[MKReverseGeocoder alloc] initWithCoordinate:userLocation.coordinate];
geocoder.delegate = self;
[geocoder start];
}
#pragma mark - Reverse geocoder delegate
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark {
[geocoder autorelease];
NSArray * formattedAddress = [placemark.addressDictionary objectForKey:@"FormattedAddressLines"];
[_selfAddress release];
_selfAddress = [[formattedAddress componentsJoinedByString:@","] retain];
MKMapView * mapView = (MKMapView *)self.view;
for (id annotation in [mapView annotations]) {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
((MKUserLocation *)annotation).subtitle = _selfAddress;
}
}
self.navigationItem.rightBarButtonItem.enabled = YES;
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error {
[geocoder autorelease];
}
#pragma mark - Private methods
- (NSString *)escapedStringFromString:(NSString *)string {
NSString * result = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL, /* allocator */
(CFStringRef)string,
NULL, /* charactersToLeaveUnescaped */
(CFStringRef)@"!*'();:@&=+$,/?%#[]",
kCFStringEncodingUTF8);
return [result autorelease];
}
#pragma mark - Public methods
- (void)routeAction {
NSString * daddr = [self escapedStringFromString:[_pointAnnotaion subtitle]];
NSString * saddr = [self escapedStringFromString:_selfAddress];
NSString * routeURL = [NSString stringWithFormat:@"maps.google.com/maps?daddr=%@&saddr=%@", daddr, saddr];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:routeURL]];
}
@end
Related Materials
Apple Url Scheme
______________________