Skip to content

Drawing polyines or routes on a MKMapView in iOS4 – Part 3

Get right to it: here’s the sample code

With the release of iOS 4, Apple has drastically decreased the complexity involved in rendering shapes, including routes, on a map. In fact, they’ve added an entire set of classes to MapKit specifically designed to help you add shapes to a map. These new classes and protocols are:
MKCircle
MKCircleView
MKMultiPoint
MKOverlayPathView
MKPolygon
MKPolygonView
MKPolyline
MKPolylineView
MKShape
MKOverlay

These classes make it incredibly easy to do what was once complex (And slightly buggy). Before when you would scroll or zoom in on a map with a route rendered in an MKAnnotationView, the route would have to temporarily be hidden because it was not scaled or scrolled along with with map. the MapKit framework now handles all this automatically.

So how do you add a route to a map using the updated MKMapView in iOS 4?

  1. Create a MKPolyline
  2. Add the polyline (as a MKOverlay) to the map
  3. Implement mapView:viewForOverlay: in your MKMapViewDelegate
  4. Initialize, set values on, and return a MKPolylineView from your updated map view delegate
  5. Here’s some sample code that shows how you might create your MKPolyline:

    -(void) loadRoute
    {
    NSString* filePath = [[NSBundle mainBundle] pathForResource:@”route” ofType:@”csv”];
    NSString* fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSArray* pointStrings = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

    // while we create the route points, we will also be calculating the bounding box of our route
    // so we can easily zoom in on it.
    MKMapPoint northEastPoint;
    MKMapPoint southWestPoint;

    // create a c array of points.
    MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * pointStrings.count);

    for(int idx = 0; idx < pointStrings.count; idx++)
    {
    // break the string down even further to latitude and longitude fields.
    NSString* currentPointString = [pointStrings objectAtIndex:idx];
    NSArray* latLonArr = [currentPointString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@","]];

    CLLocationDegrees latitude = [[latLonArr objectAtIndex:0] doubleValue];
    CLLocationDegrees longitude = [[latLonArr objectAtIndex:1] doubleValue];

    // create our coordinate and add it to the correct spot in the array
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);

    MKMapPoint point = MKMapPointForCoordinate(coordinate);

    //
    // adjust the bounding box
    //

    // if it is the first point, just use them, since we have nothing to compare to yet.
    if (idx == 0) {
    northEastPoint = point;
    southWestPoint = point;
    }
    else
    {
    if (point.x > northEastPoint.x)
    northEastPoint.x = point.x;
    if(point.y > northEastPoint.y)
    northEastPoint.y = point.y;
    if (point.x < southWestPoint.x)
    southWestPoint.x = point.x;
    if (point.y < southWestPoint.y)
    southWestPoint.y = point.y;
    }

    pointArr[idx] = point;

    }

    // create the polyline based on the array of points.
    self.routeLine = [MKPolyline polylineWithPoints:pointArr count:pointStrings.count];

    _routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);

    // clear the memory allocated earlier for the points
    free(pointArr);

    }

    Now that you’ve got your route’s polyline created, you need to let the map view display it by adding it as an overlay:

    [self.mapView addOverlay:self.routeLine];

    Adding the overlay alone will not render anything on the map. Your MKMapViewDelegate implementation must return an overlay for this route you’ve just added. Luckily, the new iOS 4 MapKit framework provides a default implementation of an MKOverlayView that is capable of rendering a line on a map. This class is called MKPolylineView, and this is how you might use it:

    – (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id )overlay
    {
    MKOverlayView* overlayView = nil;

    if(overlay == self.routeLine)
    {
    //if we have not yet created an overlay view for this overlay, create it now.
    if(nil == self.routeLineView)
    {
    self.routeLineView = [[[MKPolylineView alloc] initWithPolyline:self.routeLine] autorelease];
    self.routeLineView.fillColor = [UIColor redColor];
    self.routeLineView.strokeColor = [UIColor redColor];
    self.routeLineView.lineWidth = 3;
    }

    overlayView = self.routeLineView;

    }

    return overlayView;

    }

    I hope this helps you use the new MapKit classes. Apple really has simplified rendering lines, routes and other shapes on the map, and these new classes and protocols really deprecate my previous articles on the topic, so long as you are targeting iOS 4 and above.

    Here a sample project that uses the above code:
    os4Maps

{ 31 } Comments

  1. Joerg | July 13, 2010 at 12:48 am | Permalink

    A working solution can be found here: http://github.com/mobilemelting/nvpolyline
    This solution is especially targeted at iPhone OS versions prior to v.4.0

    Although it can also be used in v.4.0

  2. Marcin Zbijowski | July 16, 2010 at 6:04 am | Permalink

    That’s very cool solutions but in real life app, how would you get the route location points between start and destination?

  3. admin | July 16, 2010 at 7:50 am | Permalink

    Marcin,

    The points I used in this demo were obtained by using the core location manager. You could do something similar and then generate your list of points from the incoming data from the core location manager.

    -Craig

  4. Yoon Lee | July 24, 2010 at 12:40 am | Permalink

    Hello, this is great example!!
    I’ve run build and analyze found leaks…

  5. michael | July 30, 2010 at 3:10 pm | Permalink

    thank you I was freaking out when i updated my xcode to 4.0

  6. Matteo | September 15, 2010 at 12:39 am | Permalink

    Hi, great job!

    One question: it’s possible to draw a route with two (or more) different colors?

    thanks,

    Matteo

  7. admin | September 15, 2010 at 9:44 pm | Permalink

    Matteo,

    You can probably split you route into two separate segments and use a different color for each.

  8. Claus Guttesen | September 20, 2010 at 3:52 am | Permalink

    Hi Craig. Read your previous polyline-posting which I implemented. But I couldn’t scroll the map after the line was drawn. Then I read your updated post and after some mixin it works like a charm.

  9. Anand Agarwal | September 28, 2010 at 6:15 am | Permalink

    Hi,

    Good job! I was using the previously posting for route drawing mechanism, it was working fine on the all version below ios 4.0. As I switched my project to ios 4.0 I could not scroll map even I can zoom the app. Then I found new posting regarding this. I implemented that and it is working fine on the ios 4 devices. But what I want base SDK 4.0 and deployment target 3.1.2 for my app since I want app would run on the user device who have min version 3.1.2. After doing this when I tried to install this sample code on the device 3.1.2 and baseSDK 4.0 then this sample app installed but it crashes on the home screen. And in the console I am getting following error message.
    dyld: Symbol not found: _OBJC_CLASS_$_MKPolyline
    Referenced from: /var/mobile/Applications/211F44E6-B9AD-451B-A636-0147EE2AD38F/os4Maps.app/os4Maps
    Expected in: /System/Library/Frameworks/MapKit.framework/MapKit
    in /var/mobile/Applications/211F44E6-B9AD-451B-A636-0147EE2AD38F/os4Maps.app/os4Maps
    Data Formatters temporarily unavailable, will re-try after a ‘continue’. (Not safe to call dlopen at this time.)
    mi_cmd_stack_list_frames: Not enough frames in stack.
    mi_cmd_stack_list_frames: Not enough frames in stack.

    Can anybody suggest me pls how can I run this route drawing code for version 3.1.2 or above like 4.0 both.

    Thanks

  10. Lauri Larjo | October 24, 2010 at 5:47 am | Permalink

    Anand,
    these were additions to the SDK that came with the iOS4 update. You cannot use this code and MKPolyline classes if you want to run it on version 3.1.2, because they don’t support it. If you want to display routes on both versions 3 and 4, then you can use the aforementioned NVPolyline project: http://github.com/mobilemelting/nvpolyline

  11. Kash. | November 30, 2010 at 3:09 pm | Permalink

    Craig thanks for this tutorial!

  12. Dev | December 6, 2010 at 5:11 am | Permalink

    The points I used in this demo were obtained by using the core location manager. You could do something similar and then generate your list of points from the incoming data from the core location manager.

    Please Admin how can I get points.

  13. eric m | January 7, 2011 at 7:38 pm | Permalink

    spent all day on this…..apple should hire you to write their docs…thanks!!

  14. Raj | March 1, 2011 at 6:21 am | Permalink

    Thanks for the article. I have been struggling to use Overlays to display predefined map points on Augmented Reality. I am getting all points perfectly but getting frames overlapped with each other when they are close together. I have latitude, longitude for each map points. Could you please help me to create the overlayview for these point to get rid of overlapping windows please?

    Thanks.

  15. Joe | March 2, 2011 at 7:08 am | Permalink

    Great example thanks… I’ve been trying to fathom this out for weeks but with no joy! Apple only supply KMLViewer as sample code, and it’s so overloaded with KML stuff that it doesn’t help with the actual MKPolyline code. If only they used your example instead!

    One thing if I may simplify it though… You create your own bounding rect for the map area when it’s already built into MapKit:

    // zoom in on the route.
    [self.mapView setVisibleMapRect:[self.routeLine boundingMapRect]];

    Which removes about 10 lines of code for you :)

    Thanks again!!

    :-Joe

  16. Joe | March 2, 2011 at 7:21 am | Permalink

    Also, you don’t need to convert the coordinates to MKPoints:

    // create an array of points.
    CLLocationCoordinate2D coordinateArray[pointStrings.count];

    for(int idx = 0; idx < pointStrings.count; idx++)
    {
    // break the string down even further to latitude and longitude fields.
    NSString* currentPointString = [pointStrings objectAtIndex:idx];
    NSArray* latLonArr = [currentPointString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@","]];

    CLLocationDegrees latitude = [[latLonArr objectAtIndex:0] doubleValue];
    CLLocationDegrees longitude = [[latLonArr objectAtIndex:1] doubleValue];

    // create our coordinate and add it to the correct spot in the array
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);
    coordinateArray[idx] = coordinate;
    }

    // create the polyline based on the array of coordinates:
    self.routeLine = [MKPolyline polylineWithCoordinates:coordinateArray count:pointStrings.count];

    Hope this helps :)

  17. Raj | March 2, 2011 at 11:45 am | Permalink

    Joe, nice one. I have also noticed this from KMLViewer example. Do you have any idea on how would you create OverLaysView for rectangles? See my complete question above just before your first response.

    Thanks.

  18. Damien | April 7, 2011 at 5:22 pm | Permalink

    Hi,
    I use your solution to draw routes in my MKMapView, but I’d like to add a gradient in it depending on the speed (coming from a CLLocationManager). Do you have any idea how to make it this way?
    I posted something here: http://stackoverflow.com/questions/5479833/iphone-gradient-in-mapview-overlay showing a screenshot of what I’d like to have exactly…
    Otherwise, great post and it helped me a lot, thanks!

  19. michael | April 20, 2011 at 10:58 am | Permalink

    Overlays are great as long as you don’t plan on moving them lets say with your finger across the screen. The refresh rate using method calls like setNeedsDisplay are much slower between redraws than using your previous route annotation implementation.

    Summary from my experience –

    Overlays:
    + placing objects between the map and annotations
    + iOS handles map changes/redraw method calls, including resizing views
    – high redraw rates are hard to achieve (i.e. ability to drag an overlay smoothly across the screen which is drawn using CGContext methods)

    Annotations (using your subview logic):
    + placing objects on the map for which the region/size is known
    + objects drawn stay the same pixel size by default
    – having to manage map view region change events and annotation view visibility
    – if you draw some minor distance outside of the original annotation view, iOS will no longer call your drawRect method after setNeedsDisplay. This is mostly likely due to some internal draw performance logic. An example using the route annotation subview project is posted as a comment here: http://spitzkoff.com/craig/?p=108&cpage=2#comment-509

    Thanks for all the help Craig.

  20. JJSaccolo | July 11, 2011 at 11:04 am | Permalink

    hi!
    I have a problem: my route goes from australia to america, but I want that it passes from the ocean, not “from europe”… do you understand?

    Well, the problem is that from the most east point to the most west point, there is an horizontal line… really ugly!

    what can I do?
    Thanks!

  21. F. Bartolomucci | October 6, 2011 at 12:03 pm | Permalink

    So far I have been able to include your code in my project and show your polylines where it belongs, still I am not totally clear about how to “refresh” the overlay view in order to show artifacts that have been programmatically added to it.

  22. ACFC | November 8, 2011 at 1:20 am | Permalink

    I just trying too implement your sample in my application. It is now showing the right position of my route on the Display (my route is in the center) but it does’n draw the route…, but if I load my route in your project it works… At the moment my Project is an Standard,Sattelite and Hybrid show of the map, but if I use your example to add to my code, after the build it shows the right position of my route, but it doesn’t draw the route… Can you help me please,so soon as possible?
    Thanks!

  23. Ron Hooper | December 1, 2011 at 10:21 am | Permalink

    I am having the same problem as ACFC. My app gets the points for a route from google. The response also contains the bounding rect. My app creates the rect and shows the map correctly and I add the overlay. The – (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id )overlay is called and I construct a MKOverlayPathView from the points. I have verified that the points are within the bounding rect. However, the route overlay does not draw on the map.

    I have checked everything I can think of with no joy, any suggestions would be greatly appreciated. Thanks

  24. milonet | December 19, 2011 at 2:26 pm | Permalink

    hi all,
    it’s normal that in ios5 not work?
    the delegate method isn’t called :(

    i put an nslog in a delegate method – (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id )overlay
    but not work.. how can resolve?

    thank you

  25. Sam | December 30, 2011 at 3:44 am | Permalink

    Thanks for the article. It helped me a lot in my app which shows the route to the user.

    One question though, on the map, if the two points are really far, it draws a straight line, which makes it look ugly because it does not follow the route. I believe there cannot be any solution to this – but just thought I’d mention it.

    Thanks, again.

  26. Jam | January 5, 2012 at 3:23 am | Permalink

    Why

    MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * pointStrings.count);

    and not:

    MKMapPoint* pointArr = malloc(sizeof(MKMapPoint) * pointStrings.count);

  27. Christopher | January 22, 2012 at 2:41 pm | Permalink

    Would you agree that there is a one-to-one relationship between a MKOverlay and a MKPolylineView? If so, doesn’t this go against the re-use of views paradigm? Just curious.

  28. rich4321 | March 4, 2012 at 8:08 am | Permalink

    This is otherwise a good tutorial except for one issue. The sample code on line 95 In os4MapViewController.m, _routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x – southWestPoint.x, northEastPoint.y – southWestPoint.y); Xcode keeps telling me that “The left operand of ‘-‘ is a garbage value”
    Does anyone know how to fix it?

  29. jayesh | June 4, 2012 at 7:55 am | Permalink

    CLLocationDegrees longitude = [[latLonArr objectAtIndex:1] doubleValue];

    throwing error on aboveLine;

  30. karob | June 7, 2012 at 11:55 am | Permalink

    perfect, thank you!

  31. Ace | March 25, 2013 at 11:30 pm | Permalink

    This is probably the most clean and workable solution out there. Thanks Craig!

{ 3 } Trackbacks

  1. DanZei » Meine erste iPhone App – Folge 12 | September 26, 2011 at 5:57 pm | Permalink

    […] wirklich trivial, aber ich habe in den weiten des Internet ein gutes und vorallem übersichtliches Beispiel gefunden, das mich zur korrekten Lösung geführt hat. Der Muster Code von Apple scheint mir da […]

  2. […] am using the code in this link : http://spitzkoff.com/craig/?p=136 , I have changed it to use Google map API , here is my code […]

  3. […] vamos a utilizar las clases MKPolyline y MKOverlayView. La forma de hacerlo la he obtenido de este blog, que es donde cuentan que desde iOS 4 es fácil pero que antes era complicado e infructuoso. El […]

Post a Comment

Your email is never published nor shared. Required fields are marked *