Custom Path Between Two Locations in iPhone Map View

In this tutorial I will explain custom drawing of path between two locations in iOS map view. This is possible by using annotations, core graphics and map kit. The MapKit allows simple access to the map seen in the maps application. Using GoogleMaps as its engine the map kit allows for a developer to make their own custom map interface to fit their own application. Today I will be reviewing the MapView as well as the Map Annotations that can be used to highlight points of interest in a map. For this purpose I will create my own custom map view along with custom annotations and its views. The main source of this tutorial is from wlach/nvpolyline.git.

Create project in Xcode using single window application template and name it as MapViewSample. Before proceeding further we need to add mapKit and coreLocation frameworks to the application bundle. I am not digging into the details of adding the frameworks into the project because it is quite common in all applications. Now open ViewController.h file, import MapKit framework into it and register this class with MKMapViewDelegate protocol. Here we are not modifying any thing in .xib file of ViewController class. Add the following lines of code in loadView method of ViewCotroller.m file.

 

_mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[_mapView setDelegate:self];
[self.view addSubview:_mapView];

 

 

Build and run the code, you will see the following map view inside iPhone screen.

The main objective of this tutorial is to draw the path between any two specified locations. There is no official apple documentation to achieve this. Hence we draw the path using drawRect method of UIView class. But to draw the path using drawRect method we need the points or locations (latitudes and longitudes) where we need to initiate or extend the drawing. For this we use MKAnnotaion and MKAnnotationView classes.

Add the new class to the project, name it as MapAnnotation and subclass it with NSObject. Now open MapAnnotation.h file and add the following lines of code.

 

#import
<mapKit/MapKit.h>

@interface MapAnnotation : NSObject
{
NSArray* _points;
MKMapView* _mapView;
}

-(id) initWithPoints:(NSArray*) points mapView:(MKMapView *)mapView;
@property (nonatomic, retain) NSArray* points;

@end

 

 

Implement initWithPoints and other useful methods of MapAnnotation class as follows.

 

-(id) initWithPoints:(NSArray *) points mapView:(MKMapView *)mapView
{
self = [super init];
_points = [[NSArray alloc] initWithArray:points];
_mapView = mapView;
return self;
}

- (CLLocationCoordinate2D) coordinate
{
return [_mapView centerCoordinate];
}

 

 

To see the annotation effect, add the following code in loadView method of ViewController.m file.

[

NSArray *points = [NSArray arrayWithObjects:
[[CLLocation alloc] initWithLatitude:45.43894 longitude:-73.7396],nil];
MapAnnotation *annotation = [[MapAnnotation alloc] initWithPoints:points mapView:_mapView];
[_mapView addAnnotation:annotation];

MKCoordinateRegion region;
region.span.longitudeDelta = 0.219727;
region.span.latitudeDelta = 0.221574;
region.center.latitude = 45.452424;
region.center.longitude = -73.662643;
[_mapView setRegion:region];

 

 

Build and run the code, you will see the following map view inside iPhone screen.

We don’t need any pin effect as shown in the above screenshot. To get the desired path effect we need to add the new class named MapAnnotationView and subclass it with MKAnnotaionView. Open MapAnnotationView.h file and add the following code.

 

#import
<mapKit/MapKit.h>
#import <foundation/Foundation.h>
#import "MapAnnotation.h"

@interface MapAnnotationView : MKAnnotationView
{
MKMapView * _mapView;
UIView * _internalView;
}

@property (nonatomic) CGPoint centerOffset;
- (id)initWithAnnotation:(MapAnnotation *)annotation
mapView:(MKMapView *)mapView;

@end

 

 

The main purpose of this class is to draw the path between two locations based on its latitude and longitude using its drawRect method. Modify MapAnnotationView.m file as follows.

 

const CGFloat POLYLINE_WIDTH = 5.0;

@interface InternalAnnotationView : UIView
{
MapAnnotationView* _polylineView;
MKMapView *_mapView;
}

- (id) initWithPolylineView:(MapAnnotationView *)polylineView
mapView:(MKMapView *)mapView;

@end
@implementation InternalAnnotationView

- (id) initWithPolylineView:(MapAnnotationView *)polylineView
mapView:(MKMapView *)mapView
{
if (self = [super init]){
_polylineView = polylineView;
_mapView = mapView;

self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = NO;
}

return self;
}

-(void) drawRect:(CGRect)rect
{
MapAnnotation* annotation = (MapAnnotation*)_polylineView.annotation;
if (annotation.points && annotation.points.count > 0)
{
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
CGContextSetAlpha(context, 0.5);

CGContextSetLineWidth(context, POLYLINE_WIDTH);

for (int i = 0; i < annotation.points.count; i++) {
CLLocation* location = [annotation.points objectAtIndex:i];
CGPoint point = [_mapView convertCoordinate:location.coordinate toPointToView:self];

if (i == 0)
CGContextMoveToPoint(context, point.x, point.y);
else
CGContextAddLineToPoint(context, point.x, point.y);
}

CGContextStrokePath(context);
}
}

@end

@implementation MapAnnotationView

- (id)initWithAnnotation:(MapAnnotation *)annotation
mapView:(MKMapView *)mapView
{
if (self = [super init]) {
self.annotation = annotation;
_mapView = mapView;

self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = NO;
self.frame = CGRectMake(0.0, 0.0, _mapView.frame.size.width, _mapView.frame.size.height);

_internalView = [[InternalAnnotationView alloc] initWithPolylineView:self mapView:_mapView];
[self addSubview:_internalView];
}
return self;
}

-(void) regionChanged
{
MapAnnotation* annotation = (MapAnnotation *)self.annotation;
CGPoint minpt, maxpt;
for (int i = 0; i < annotation.points.count; i++)
{
CLLocation* location = [annotation.points objectAtIndex:i];
CGPoint point = [_mapView convertCoordinate:location.coordinate toPointToView:_mapView];
if (point.x < minpt.x || i == 0)
minpt.x = point.x;
if (point.y < minpt.y || i == 0)
minpt.y = point.y;
if (point.x > maxpt.x || i == 0)
maxpt.x = point.x;
if (point.y > maxpt.y || i == 0)
maxpt.y = point.y;
}

CGFloat w = maxpt.x - minpt.x + (2*POLYLINE_WIDTH);
CGFloat h = maxpt.y - minpt.y + (2*POLYLINE_WIDTH);

_internalView.frame = CGRectMake(minpt.x - POLYLINE_WIDTH, minpt.y - POLYLINE_WIDTH,
w, h);
[_internalView setNeedsDisplay];
}

- (CGPoint) centerOffset
{
[self regionChanged];
return [super centerOffset];
}

- (void) setCenterOffset:(CGPoint) centerOffset {
[super setCenterOffset:centerOffset];
}

@end

 

 

Now open ViewController.m file, add the following CLLocation object in its points array of loadView method.

 

[[CLLocation alloc] initWithLatitude:45.48679000000001 longitude:-73.59443],

 

 

So far we have not implemented MKMapView delegate method. Here in ViewController.m file add the following delegate method and its code.

 

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation
{
if ([annotation isKindOfClass:[MapAnnotation class]]) {
return [[MapAnnotationView alloc] initWithAnnotation:annotation mapView:_mapView];
}
return nil;
}

 

 

Build and run the code, you will see the following map view inside iPhone screen.

We have successfully drawn the path between above mentioned points using MapAnnotationView class. This path is a straight line between two specified locations. But we need the exact google map path between two mentioned locations. For this we need to add more CLLocation objects with desired latitude and longitude. Add the following CLLocation objects into the points array.

 

[[CLLocation alloc] initWithLatitude:45.44073 longitude:-73.73998],
[[CLLocation alloc] initWithLatitude:45.44082000000001 longitude:-73.73992],
[[CLLocation alloc] initWithLatitude:45.44382 longitude:-73.74069],
[[CLLocation alloc] initWithLatitude:45.44612 longitude:-73.74122],
[[CLLocation alloc] initWithLatitude:45.44612 longitude:-73.74122],
[[CLLocation alloc] initWithLatitude:45.44628 longitude:-73.74119],
[[CLLocation alloc] initWithLatitude:45.44649 longitude:-73.74106999999999],
[[CLLocation alloc] initWithLatitude:45.44665999999999 longitude:-73.7409],
[[CLLocation alloc] initWithLatitude:45.44665999999999 longitude:-73.7409],
[[CLLocation alloc] initWithLatitude:45.44676 longitude:-73.74073],
[[CLLocation alloc] initWithLatitude:45.44707999999999 longitude:-73.73990000000001],
[[CLLocation alloc] initWithLatitude:45.44748 longitude:-73.73856000000001],
[[CLLocation alloc] initWithLatitude:45.44748 longitude:-73.73856000000001],
[[CLLocation alloc] initWithLatitude:45.44834 longitude:-73.73581],
[[CLLocation alloc] initWithLatitude:45.44857999999999 longitude:-73.73475999999999],
[[CLLocation alloc] initWithLatitude:45.44863000000001 longitude:-73.73417000000001],
[[CLLocation alloc] initWithLatitude:45.44863000000001 longitude:-73.73300999999999],
[[CLLocation alloc] initWithLatitude:45.44795 longitude:-73.7008],
[[CLLocation alloc] initWithLatitude:45.44784 longitude:-73.69398],
[[CLLocation alloc] initWithLatitude:45.44775 longitude:-73.69092000000001],
[[CLLocation alloc] initWithLatitude:45.44743999999999 longitude:-73.68584],
[[CLLocation alloc] initWithLatitude:45.44728 longitude:-73.68165999999999],
[[CLLocation alloc] initWithLatitude:45.44726 longitude:-73.67901999999999],
[[CLLocation alloc] initWithLatitude:45.44713000000001 longitude:-73.67238],
[[CLLocation alloc] initWithLatitude:45.44691 longitude:-73.67075],
[[CLLocation alloc] initWithLatitude:45.44662 longitude:-73.66947],
[[CLLocation alloc] initWithLatitude:45.44555 longitude:-73.66679000000001],
[[CLLocation alloc] initWithLatitude:45.44460999999999 longitude:-73.66426],
[[CLLocation alloc] initWithLatitude:45.443 longitude:-73.65927000000001],
[[CLLocation alloc] initWithLatitude:45.44249000000001 longitude:-73.65730000000001],
[[CLLocation alloc] initWithLatitude:45.44229 longitude:-73.6563],
[[CLLocation alloc] initWithLatitude:45.44211 longitude:-73.65433],
[[CLLocation alloc] initWithLatitude:45.44192 longitude:-73.65125999999999],
[[CLLocation alloc] initWithLatitude:45.44196999999999 longitude:-73.65013999999999],
[[CLLocation alloc] initWithLatitude:45.44213000000001 longitude:-73.64919],
[[CLLocation alloc] initWithLatitude:45.44232000000001 longitude:-73.64847],
[[CLLocation alloc] initWithLatitude:45.44268999999999 longitude:-73.64754000000001],
[[CLLocation alloc] initWithLatitude:45.44527000000001 longitude:-73.64261],
[[CLLocation alloc] initWithLatitude:45.44895 longitude:-73.63585999999999],
[[CLLocation alloc] initWithLatitude:45.44991 longitude:-73.63379],
[[CLLocation alloc] initWithLatitude:45.45045 longitude:-73.63251],
[[CLLocation alloc] initWithLatitude:45.4542 longitude:-73.62442],
[[CLLocation alloc] initWithLatitude:45.4547 longitude:-73.62327000000001],
[[CLLocation alloc] initWithLatitude:45.45536000000001 longitude:-73.62194],
[[CLLocation alloc] initWithLatitude:45.46158 longitude:-73.61073],
[[CLLocation alloc] initWithLatitude:45.46158 longitude:-73.61073],
[[CLLocation alloc] initWithLatitude:45.4634 longitude:-73.60753],
[[CLLocation alloc] initWithLatitude:45.4634 longitude:-73.60753],
[[CLLocation alloc] initWithLatitude:45.46362 longitude:-73.60720999999999],
[[CLLocation alloc] initWithLatitude:45.46447 longitude:-73.60558],
[[CLLocation alloc] initWithLatitude:45.46541 longitude:-73.60393000000001],
[[CLLocation alloc] initWithLatitude:45.46653000000001 longitude:-73.60204],
[[CLLocation alloc] initWithLatitude:45.46685 longitude:-73.60166],
[[CLLocation alloc] initWithLatitude:45.46720000000001 longitude:-73.60137],
[[CLLocation alloc] initWithLatitude:45.46754 longitude:-73.60120000000001],
[[CLLocation alloc] initWithLatitude:45.4682 longitude:-73.60108],
[[CLLocation alloc] initWithLatitude:45.46870000000001 longitude:-73.60115],
[[CLLocation alloc] initWithLatitude:45.46922 longitude:-73.60138000000001],
[[CLLocation alloc] initWithLatitude:45.46972999999999 longitude:-73.60173],
[[CLLocation alloc] initWithLatitude:45.46996 longitude:-73.60209],
[[CLLocation alloc] initWithLatitude:45.4703 longitude:-73.60287],
[[CLLocation alloc] initWithLatitude:45.47117999999999 longitude:-73.60565],
[[CLLocation alloc] initWithLatitude:45.47163 longitude:-73.60674],
[[CLLocation alloc] initWithLatitude:45.47163 longitude:-73.60674],
[[CLLocation alloc] initWithLatitude:45.47236 longitude:-73.60812],
[[CLLocation alloc] initWithLatitude:45.47339000000001 longitude:-73.6103],
[[CLLocation alloc] initWithLatitude:45.47339000000001 longitude:-73.6103],
[[CLLocation alloc] initWithLatitude:45.47407 longitude:-73.60908000000001],
[[CLLocation alloc] initWithLatitude:45.47581000000001 longitude:-73.60705],
[[CLLocation alloc] initWithLatitude:45.47895 longitude:-73.60353000000001],
[[CLLocation alloc] initWithLatitude:45.48116 longitude:-73.60088],
[[CLLocation alloc] initWithLatitude:45.48517 longitude:-73.59635],

 

 

Build and run the code, you will see the following map view inside iPhone screen.

We can customise the colour and width of path we have drawn. That can be done by changing the colour in drawRect method and modifying the value of POLYLINE_WIDTH variable. Here I can make colour as RED and width as 8.0, then observe the output as follows.

Leave a Comment: