Skip to content

Drawing polyines or routes on a MKMapView (as an MKAnnotationView) – Part 2

It turns out there is a better way to display routes on a map than the last example I gave. The problem with my last example is that the route exists in a layer above the map view, resulting in the line of the route being drawn on top of any annotation views. So pins, photos and anything else expressed as an MKAnnotation would be drawn over by the routes in the layer above the map view.

The solution to this problem then is to draw the lines as usual, but in a custom MKAnnoationView. This view will be set to not clip its own subviews, and will then have a subview that lies outside the frame of the annotation view, and this subview will draw the lines. The route annotation view needs a subview to do the rendering which will always be positioned at the full frame size and origin of the map. This way the MKAnnotationView can be smaller than the route, but it  always draws in the internal subview, which is the size of the map view.

So… long story short: the ability to put a route on a MKMapView via a custom MKAnnotation. You can download the sample code here.

//
// CSRouteView.m
// testMapp
//
// Created by Craig on 8/18/09.
// Copyright Craig Spitzkoff 2009. All rights reserved.
//
#import "CSRouteView.h"
#import "CSRouteAnnotation.h"
// this is an internally used view to CSRouteView. The CSRouteView needs a subview that does not get clipped to always
// be positioned at the full frame size and origin of the map. This way the view can be smaller than the route, but it
// always draws in the internal subview, which is the size of the map view.
@interface CSRouteViewInternal : UIView
{
// route view which added this as a subview.
CSRouteView* _routeView;
}
@property (nonatomic, retain) CSRouteView* routeView;
@end
@implementation CSRouteViewInternal
@synthesize routeView = _routeView;
-(void) drawRect:(CGRect) rect
{
CSRouteAnnotation* routeAnnotation = (CSRouteAnnotation*)self.routeView.annotation;

// only draw our lines if we're not int he moddie of a transition and we
// acutally have some points to draw.
if(!self.hidden && nil != routeAnnotation.points && routeAnnotation.points.count > 0)
{
CGContextRef context = UIGraphicsGetCurrentContext();

if(nil == routeAnnotation.lineColor)
routeAnnotation.lineColor = [UIColor blueColor]; // setting the property instead of the member variable will automatically reatin it.

CGContextSetStrokeColorWithColor(context, routeAnnotation.lineColor.CGColor);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);

// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);

for(int idx = 0; idx < routeAnnotation.points.count; idx++)
{
CLLocation* location = [routeAnnotation.points objectAtIndex:idx];
CGPoint point = [self.routeView.mapView convertCoordinate:location.coordinate toPointToView:self];

NSLog(@"Point: %lf, %lf", point.x, point.y);

if(idx == 0)
{
// move to the first point
CGContextMoveToPoint(context, point.x, point.y);
}
else
{
CGContextAddLineToPoint(context, point.x, point.y);
}
}

CGContextStrokePath(context);

// debug. Draw the line around our view.
/*
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 0, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, 0);
CGContextAddLineToPoint(context, 0, 0);
CGContextStrokePath(context);
*/
}

}
-(id) init
{
self = [super init];
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = NO;

return self;
}
-(void) dealloc
{
self.routeView = nil;

[super dealloc];
}
@end
@implementation CSRouteView
@synthesize mapView = _mapView;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
// do not clip the bounds. We need the CSRouteViewInternal to be able to render the route, regardless of where the
// actual annotation view is displayed.
self.clipsToBounds = NO;

// create the internal route view that does the rendering of the route.
_internalRouteView = [[CSRouteViewInternal alloc] init];
_internalRouteView.routeView = self;

[self addSubview:_internalRouteView];
}
return self;
}
-(void) setMapView:(MKMapView*) mapView
{
[_mapView release];
_mapView = [mapView retain];

[self regionChanged];
}
-(void) regionChanged
{
NSLog(@"Region Changed");

// move the internal route view.
CGPoint origin = CGPointMake(0, 0);
origin = [_mapView convertPoint:origin toView:self];

_internalRouteView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height);
[_internalRouteView setNeedsDisplay];

}
- (void)dealloc
{
[_mapView release];
[_internalRouteView release];

[super dealloc];
}
@end

{ 62 } Comments

  1. Jean-Baptiste LE STANG | August 20, 2009 at 5:17 pm | Permalink

    Very nice follow up! This technique is really good when you have really few routes to display. I’m currently working on an application that requires sometimes nearly 500 routes. If you create an MKAnnotationView per route then you’ll get bad performances when executing the application. You can solve this by having only one MKAnnotationView centered on the MKMapView, then you add several routes (as you’re doing with an NSArray for example), then you override the drawRect method and then you can draw all the routes you have. The drawRect method can also be tuned so that you only display routes that will be really visible on the screen.

  2. Shoaib | August 25, 2009 at 10:13 pm | Permalink

    Thanks man,

    This is very nice information and it solved my big trouble.

  3. Ben Clayton | August 28, 2009 at 1:16 pm | Permalink

    Yeah this is excellent. Thanks! I was really disappointed that 3.0 didn’t have polyline drawing by default :-)

  4. code ninja | September 4, 2009 at 1:31 am | Permalink

    nice work…was working on the same thing, but using my data from google local search….i’m going to use and build off of your MKAnnotationView….in the interim, feel free to join my developer network. Thx.

  5. code ninja | September 4, 2009 at 1:50 am | Permalink

    Interesting code…I liked the switcher code in your mapLinesViewController to detect the pin type–very cool. And the route coordinates converted to CLLocations–good job.

    If you could figure out dynamic route tracking, base it on coordinates, and convert your coordinates on the fly–you can do your own pt-to-pt navigation!

  6. kish | September 22, 2009 at 7:40 am | Permalink

    hi,

    i have problem in displayind calloutview (bubble) ..i don’t know how to make it customize .i am getting default with title.i tried using leftcallout accessory view
    its adding a view but it looking odd..can any one help??
    thanks in advance

  7. Ed | October 1, 2009 at 2:49 am | Permalink

    I was using this code and ran into a problem with my annotation’s callout accessory buttons not being handled properly. I believe it was due to the internal route view taking the input. Adding this line:

    [self setUserInteractionEnabled:NO];

    to the CSRouteView init method appears to fix the problem.

  8. anand agarwal | October 1, 2009 at 3:35 am | Permalink

    Hi All,

    I want to show user current location on map using this code but when try to do this using
    _mapView.showUserLocation=YES;
    It is giving me error.

    -[MKUserLocation annotationType]: unrecognized selector sent to instance 0x104cf30
    2009-10-01 13:04:41.082 mapLines[1115:20b] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -[MKUserLocation annotationType]: unrecognized selector sent to instance 0x104cf30′
    2009-10-01 13:04:41.082 mapLines[1115:20b] Stack: (
    807902715,
    2492227131,
    808284155,
    807854166,
    807706786,
    10205,
    874128122,
    874112507,
    874096995,
    873955568,
    874083280,
    874086305,
    808000523,
    873911044,
    873917914,
    873916994,
    839246475,
    839217747,
    839235858,
    807687328,
    807683624,
    839142449,
    839142646,
    814752238
    )
    So I am not able to do this. If any body have done this. please tell me how can I achieve this.

    Other wise it is really excellent job in new updates.

  9. Rupert | October 2, 2009 at 2:10 am | Permalink

    Well done Craig. Just wanted you to know that I have used your technique and extended it to accomodate polygons as well.

    http://www.gisnotes.com/wordpress/2009/10/iphone-devnote-14-drawing-a-point-line-polygon-on-top-of-mkmapview/

    Best Regards,
    Rupert

  10. Charlie Mezak | October 5, 2009 at 11:38 am | Permalink

    @ Anand: Why don’t you just ask if ([annotation isMemberOfClass:[MKUserLocation class]]) and if so, forgo the rest of the delegate method.

    I have my own problem. The RouteView is initializing with a non-origin frame. When the map appears, the route is cut off at the top. Once the regionChanged method is called, the route draws fine, but I can’t figure out how to get the route to draw correctly the first time. Anyone have some insight on this?

    – Charlie

  11. kish | October 12, 2009 at 5:39 am | Permalink

    I was also curious as to whether its possible to extend CSMapAnnotation to have a bigger popup(callout) view…not just only to include a title, subtitle, accessoryviews, but also a bigger content callout…basically a larger view with more content….does MKAnnotation smart enough to scale that view callout to be bigger? becoz default callout has constant height …

    thsnks in advance

  12. rahulvyas | October 14, 2009 at 8:06 am | Permalink

    from where you are generating csv. is there a way that we can capture the current position and them draw the route from a fixed location.

  13. rahulvyas | October 14, 2009 at 11:00 am | Permalink

    how to create points as you have created in route.csv between two distances

  14. Matt Arturi | October 15, 2009 at 11:58 am | Permalink

    @ Charlie: I had the same problem that you referred to but I came up with this simple workaround that forces the route to be redrawn. Just create a function that nudges the map ever so slightly and have your view controller execute it after an imperceptible delay. Here’s the code:


    - (void) nudgeMap {
    CLLocationCoordinate2D newCenter;
    newCenter.latitude = _mapView.region.center.latitude - .000017;
    newCenter.longitude = _mapView.region.center.longitude;
    [_mapView setCenterCoordinate:newCenter animated:YES];
    }

    And here’s the call from my view controller’s viewWillAppear method:


    [self performSelector:@selector(nudgeMap) withObject:nil afterDelay:.01];

    That’s it. BTW – Great work Craig!

  15. David Manpearl | October 22, 2009 at 9:32 pm | Permalink

    Dear Craig,
    First of all, thank you very much for your excellent example for drawing polylines and routes on a MKMapView (as an MKAnnotationView). I am using it to draw routes, icons, annotations, and even Pins on an MKMapView.

    However, I am having a serious problem with the order of my UIAnnotationView objects. I cannot control which objects are displayed on top of each other and the order seems random, perhaps based on the NSMutableDictionary keys and perhaps based on the reuse views controlled by dequeueReusableAnnotationViewWithIdentifier. When I use your example as-is, the view seems correct – the route is on the bottom, the Cleveland Circle image is above that, and callouts are displayed above that. But, as soon as I dramatically change the coordinates of the route allowing it to cross itself and randomly jump around, the object display order seemingly becomes random.

    Please help with some advice to guide my efforts to force my routes to be drawn on the bottom (directly above the map), images on top of that, and of course MKAnnotation callouts on top of everything.
    – Thank you and Best Regards, David

  16. Matej Bukovinski | October 26, 2009 at 2:08 pm | Permalink

    David, adding the following lines to regionChanged solved the overlapping problem for me (note that I have only one path annotation visible at a time):

    // Check if path below other annotations and move it below if not
    UIView *parent = self.superview;
    if (parent && [parent.subviews indexOfObject:self] != 0) {
    [self removeFromSuperview];
    [parent insertSubview:self atIndex:0];
    }

    I also noticed that it’s worthwhile removing the delegate methods (regionWillChange, regionDidChange) and do the
    regionChanged call inside CSRouteView:

    – (void)setCenter:(CGPoint)c {
    [super setCenter:c];
    [self regionChanged];
    }

    There is no need to hide the path this way and I and I haven’t noticed any drastic speed decreases. If you increase the _internalRouteView frame to draw a bit off-screen than you can have an even better experience, i.e.:

    #define REGION_INC 500

    _internalRouteView.frame = CGRectMake(origin.x – REGION_INC, origin.y – REGION_INC, _mapView.frame.size.width + 2*REGION_INC, _mapView.frame.size.height + 2*REGION_INC);

  17. iGypsy | October 26, 2009 at 4:45 pm | Permalink

    Thanks Matej! The polygon extension to this code provided by Rupert had this problem, I assume because the “route” always overlaps itself when drawing a polygon. I was scratching my head for some time trying to figure out how to make this draw on the bottom.

  18. Matej Bukovinski | October 27, 2009 at 6:59 am | Permalink

    This setCenter override method may not be as good as I firstly thought. On the device (not in the simulator) I noticed that some tiles were failing to load every now and then when scrolling around with a visible path. The console output contained the following line for every failed tile:

    bool GMM::TileResponse::createNextImage(Tile*, CGImage**): ERROR! image is 0

    Has anyone got any ideas what the reason for this could be?

    I tried changing the method in order to delay the drawing after the centering takes place:

    – (void)setCenter:(CGPoint)c {
    [super setCenter:c];
    [self performSelector:@selector(regionChanged) withObject:nil afterDelay:1];
    }

    and it seems to help .

  19. dmanpearl | October 27, 2009 at 2:46 pm | Permalink

    Dear Matej,
    Thank you for regionChanged suggestion regarding the drawing order. This method works for me after the user physically moves or zooms the map. However on initialization after viewDidLoad, the annotation orders are initially incorrect. A direct call to regionChanged from within viewDidLoad does not help.
    I was able to implement a kludge work-around with your delay suggestion above by calling regionChanged after a delay from RouteView’s initialization initWithFrame:
    [self performSelector:@selector(regionChanged) withObject:nil afterDelay:0.1];
    This is obviously not an optimal solution.
    How do you suggest solving the order issue on initialization?
    – Thanks, David

  20. Matej Bukovinski | October 28, 2009 at 10:13 am | Permalink

    Hi David,

    I’m well aware that performSelector:withObject:afterDelay: is a submittal solution (or let’s rather call it a workaround). Calling it in setCenter does work fine for mowing along the path but produces a noticeable delay when zooming. I would much rather call the method instantly but then tiles fail to load. I have no idea why.

    CSRouteView’s viewDidLoad method should get called well before it’s inserted to the annotation view hierarchy (you probably call it in the mapView:viewForAnnotation: callback method).

    You could try overriding UIView’s didMoveToSuperview:

    – (void)didMoveToSuperview {
    UIView *parent = self.superview;
    if (parent && [parent.subviews indexOfObject:self] != 0) {
    [self removeFromSuperview];
    [parent insertSubview:self atIndex:0];
    }
    }

    Does this help? I haven’t noticed this since my map gets moved as soon as the path gets drawn.

  21. Cheryl | October 28, 2009 at 1:15 pm | Permalink

    Hi

    Thanks for this tutorial it helped me alot. I am successfully drawing annotations on a map using an array of annotations. I can even click on the annotation and change it’s colour or image. My problem arises when the use selects the second annotation and I want to dynamically change the colour or image of the first one back to a non-selected colour/image. I can get the array of all the annotations and work through the array but once I try to set the colour or image ot the array I get a similar error as encountered by anand agarwal above. Any ideas on how best to do this?

    thanks in advance
    Cheryl

  22. dmanpearl | November 9, 2009 at 12:18 am | Permalink

    Dear Craig and Matej,
    Thanks to both of you for your examples and suggestions. I have a related problem that I hope you can help with.

    I must convert the MKMapAnnotationView polylines into buttons. To do this I must trap touch events with something akin to touchesBegan, touchesMoved, and touchesEnded, convert them into latitude/longitude coordinates, test their proximity to each of my line segments, and determine if any is within a reasonable threshold distance to qualify as touched. In order for the MKMapView to continue working, I also have to pass all the touch events into the map view. I have done each of the steps above.

    Unfortunately, it turns out that MKMapView must be the First Responder. I discovered that the map view loses its ability to zoom because it apparently doesn’t receive multi-touch events properly when not receiving them as First Responder. Do you know how to trap touch events AND properly service the MKMapView?
    – Thank you, David

    P.S. Does anyone know how to change my settings to get email notifications when this thread is updated?

  23. Matej Bukovinski | November 11, 2009 at 5:20 am | Permalink

    Hi,

    Instead of trapping the events as they move down the responder chain, you could process them at their origin. All those touches*: methods get called from UIwindow’s sendEvent: method. Basically you create a custom UIWindow subclass and override the sendEvent: method (remember to call super). You can forward the event to your code for processing and the super call will take care of the responder chain.

    This principle was discussed in a WWDC 2009 session (Session 103 – Processing Multi-Touch Events on iPhone). If you have access to those videos, take a look.

    I don’t know about email subscriptions but comment RSS works fine for me.

    Regards,
    Matej

  24. ashwin | November 13, 2009 at 4:02 am | Permalink

    how do i search?
    Drawing polyines or routes on a MKMapView (as an MKAnnotationView) – Part-1

  25. ashwin | November 13, 2009 at 4:34 am | Permalink

    HI, guys
    I am beginner to iphone. pls anyone tell me steps for creating this program.

  26. Chris Van Buskirk | November 13, 2009 at 8:21 am | Permalink

    Has anyone gotten a commercial app approved using this method? Any rejections. I want to show routes in my app, but they are neither walking or driving routes.

  27. admin | November 13, 2009 at 8:28 am | Permalink

    Chris,

    I’ve used this method in several apps, including RunKeeper (pro and free) as well as Walking Cinema: Murder on Beacon Hill

    -Craig

  28. Chris Van Buskirk | November 18, 2009 at 9:22 am | Permalink

    Thanks craig, I will look em up.

  29. Jason | November 24, 2009 at 2:41 pm | Permalink

    I have tried using this and I get a mixed bag for results. Sometimes all of my routes are displayed on top of my other annotations and sometimes just a few of them are on top. If the map loads and any of them are on top, none of my callout buttons work (probably because the route views are blocking it)

    Has anyone found a solid way to do this?

  30. Juho | November 30, 2009 at 1:11 pm | Permalink

    Jason,

    I’ve found out the same problem, the annotations seem to be in relatively random order, not necessarily in the order they were added. I think this is especially true if they are added in very fast pace. I resolved the solution myself by adding something like:

    – (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
    for (int i=0; i<[views count]; i++) {
    MKAnnotationView *view = [views objectAtIndex:i];
    if ([view isKindOfClass:[ViewAnnotation class]]) {
    [[view superview] sendSubviewToBack:view];
    } else {
    [[view superview] bringSubviewToFront:view];
    }
    }
    }

    Where "ViewAnnotation" is the "CSRouteView" of the example. The pins are still in random order relative to each other, but at least they are never under my route. The solution seems to work quite well, tell me if there are issues.

  31. iphone coder | December 10, 2009 at 3:53 am | Permalink

    Hi,

    Still i can not tap on annotation view and it is not going to details view in updated code also.

    Is there any different code.

    Regards,

  32. Daniel Wood | December 11, 2009 at 7:57 am | Permalink

    Juho,

    That code worked for me, however, if you remove the else block the pins stay in the correct order and it just moves the routes to the back behind everything else.

    – (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
    for (int i=0; i<[views count]; i++) {
    MKAnnotationView *view = [views objectAtIndex:i];
    if ([view isKindOfClass:[ViewAnnotation class]]) {
    [[view superview] sendSubviewToBack:view];
    }
    }
    }
    }

  33. Vladimir | December 23, 2009 at 3:35 am | Permalink

    Hello,

    thanks for example. I don’t get: how do I add a driving or walking route to my app with MapKit ?

  34. Noam | December 31, 2009 at 1:15 pm | Permalink

    Hello Craig and Matej,
    Thank you SO much for your contribution to the iPhone development world.
    I downloaded your source code for this project and I have a problem with the RouteAnnotation. I have the [points] array set up all correctly. When I first enter the application, the line is drawn correctly with my points. However, when I move the map with my finger (RegionChanged is called?), the line appears to extend all the way across the map. Would you guys know why this is happening?

    Thanks,
    Noam.

  35. petem | January 2, 2010 at 11:27 pm | Permalink

    Brilliiant. I’ve been wanting to color polyons as annotations on my maps. I incorporated this work and it works very well. My app was pretty ordinary without them. Thanks very much.

  36. petem | January 3, 2010 at 7:10 pm | Permalink

    Yes I still love this but I have a little problem. I noted the order of annotation addition, so that the latter ones wouldn’t get covered over. I’d doing this. I’m using both your path annotations and the Apple “pin” annotation on a single map. But the path area is around and very close to the pin location. The pin annotation callout has a right disclosure button. If I include the new path annotations I’m unable to activate the disclosure button action unless I zoom in very close so that the path annotation is off the map. So the path annotations are making my button inaccessible.

    I’d like to solve this problem. Would it make sense to have the path annotations (associated with the pin) respond to the touch event? If so then how? Or can we limit the size of the path annotation “rectangle”?

    Other ideas appreciated. Thanks.,

  37. petem | January 3, 2010 at 7:25 pm | Permalink

    I should have read the comments before more carefully. Thank you Daniel Wood, your piece of code does the trick. Thank you.

  38. rhabot | January 12, 2010 at 1:42 am | Permalink

    This piece of code works great!
    The only flaw I could find is the amount of memory leakage…Why is the source code (that you provided) leaking so much?
    Do you know how to fix it?

    Thanks,
    rhabot.

  39. jacobms | February 2, 2010 at 6:13 pm | Permalink

    Hi,
    first off thanks to Craig for the original code and Matej for following up with lots of great answers.

    I have a particular problem. I have created a number of annotations to hold polygons different. I want to detect touches inside these polygons. This works fine when listening for touchesEnded inside CSRouteView.

    The problem occurs when I zoom on the map. After a number of zoom ins (4-5) the touchesEnded isn’t called anymore.

    To replicate, use the original code of this post and change the background color in line 103 of CSRouteView.m to e.g. blueColor. Run the app in the simulator and notice how the whole screen is blue. Now try to to zoom once in the right side of the map and some of the map is now shown.

    touchesEnded will be called when clicking on the blue part of the screen, but not the clear part…

    Do you guys have a fix for this? I guess it’s got something to do with the size of the CSRouteView?

    Alternatively, I’ll look into subclassing UIWindow, but it seems silly to do this when it actually works quite well – apart from some details when zooming…

    Thanks in advance,
    Jacob

  40. FMulder | February 10, 2010 at 4:57 am | Permalink

    Hi, thanks a lot for this code.
    I have a question… I’m writing an app that needs a costant update of the overlaying track.
    I thought about two methods that doesn’t need an edit of your code.
    1) Right now, I get this working by removing the annotation, then calculating it again with the new path points and then adding it again to the mapview. But it gets laggy when there are many points and I’m moving the map.
    2) If I add an annotation for every track segment many routes are not showed correctly, and I also think that it would be a waste of memory.

    What do you think about it? do you have other (better) solutions like this or do you think it is better to edit your code for my purpose?

    thank you so much

  41. Philip | February 12, 2010 at 6:05 pm | Permalink

    Hi!

    Thank you for providing this elegant solution. I have the same problem as Noam. When moving or zooming the map the methods for redrawing the path isn’t called. Can anyone help?

    Thanks

    Philip

  42. Ian | February 19, 2010 at 7:20 am | Permalink

    This has been a fantastic help – thank you all.

    I’m still struggling with the issue of the view position being wrong the first time the map is shown. It appears to be out by the size of any bars at the top of the display (in the example code – the height of the very top bar with the battery indicator etc in, in my code, that plus the navigation bar I have).

    I’ve tried the “nudge” workaround but it doesn’t seem to do anything. Any ideas ?

    Any thoughts on a fix to the root problem of this ? (If I solve it I’ll post!)

    Best regards to all,

    Ian

  43. Ian | February 19, 2010 at 7:42 am | Permalink

    OK this seems to fix the offset view issue. Change:

    CSRouteView* routeView = [[[CSRouteView alloc] initWithFrame:CGRectMake(0, 0, _mapView.frame.size.width, _mapView.frame.size.height)] autorelease];

    to:

    CSRouteView* routeView = [[[CSRouteView alloc] initWithFrame:CGRectMake(-1, 19, _mapView.frame.size.width, _mapView.frame.size.height)] autorelease];

    in mapLinesViewController.m

    Is there a better way to get the constants for the status bar size without using hard coded numbers like this though ?

    Regards,

    Ian

  44. john | February 28, 2010 at 5:24 am | Permalink

    Hey,

    Where could I get my routing info from?
    Google prohibits the use of it’s api as far as i know. Cloudmade is far too expensive.
    Any ideas?

    Thanks!

  45. john | March 8, 2010 at 2:54 am | Permalink

    There’s a little bug. Problably one knows how to fix that.
    If both Pins (The Userlocation & the saved location) is on the map(visible) the map doesn’t load its content. If one of the pins is outside the view it works. Any suggestions? :)

    thanks a lot.

  46. lee | March 15, 2010 at 11:37 am | Permalink

    Dear
    Thanks ` you for your examples . I have a related problem that I hope you can help with.
    If I changed the code:

    annotation = [[[CSMapAnnotation alloc] initWithCoordinate:[[points objectAtIndex:points.count -1] coordinate]
    annotationType:CSMapAnnotationTypeImage
    title:@”Cleveland Circle”] autorelease];

    then,the image is not on the top of the view.I think this problem is caused due to the drawRect function.But I can not solve this problem until now.Sincerely hope that with your help

  47. Nik | March 15, 2010 at 6:30 pm | Permalink

    Hi craig,

    I’am glad there are such good developers to help all of us with less experience.
    Your code was really helpful and very well documented.

    I had a little problem, which i would like to mention.
    When testing my app on a device and navigating on the map the app crashes unexpectedly.

    I use a dictionary to keep the route annotations but i think there is a problem with the autorelease issue…

    I solved the problem by retaining the CSRouteAnnotations and releasing them on the dealloc method manually.

    What do you think about it?

  48. Ankush | April 5, 2010 at 8:11 am | Permalink

    Hi,

    Thanks for creating such a nice application.
    I have used your code for my application and facing a problem i.e. when I plot the route from “San Jose” to “New York”, it works fine but when I try to zoom in or zoom out, then close the application without waiting for the zoom effect. After that I re-launch the application immediately my application crashes!!! :((

    What I figured out is that when I zoom-in or zoom-out multiple drawRect events are pushed for CSRouteView from setCenter method which are still pending for the execution, when i close the application and relaunch it I somehow gets the same process back again and my application try to handle the remaining events then shuts down.

    Please help me to resolve this issue.

  49. 知识 | May 27, 2010 at 11:07 am | Permalink

    顶~~~~好文章知识分享 我会经常来的252

  50. Vijay | June 3, 2010 at 4:35 am | Permalink

    I am having a problem with dragging pin when overlapped with other pin. It got stuck after lifting the pin.

  51. adriaan | June 17, 2010 at 6:20 am | Permalink

    It sounds like I have the same issues as @Charlie Mezak and @FMulder.
    What I’m trying to do is to display an evolving route. Most of it’s static but the last coordinate is extrapolated based (as if something is moving at a certain speed). I am trying to accomplish this with two routes – one for the static component and one for the extrapolated bit.

    The problem I’m having is as Charlie said that “The RouteView is initializing with a non-origin frame” which is causing the extrapolated route to get cropped in certain situations. And because I keep redrawing/extrapolating this every second, it gets re-cropped every second.

    Does anyone have an idea on how to get the RouteView to initialize correctly, i.e. aligned with the frame of the mapview?

  52. Chris | June 26, 2010 at 12:44 pm | Permalink

    Thanks allot for your code.

    Is there anybody out there who managed displaying routes with allot of single points?
    This will be extremely slow – and blocks the mainthread, too.

    Any Ideas?

  53. Merlin | July 3, 2010 at 4:14 pm | Permalink

    I’m using the code from Matt Arturi’s comment to fix the clipping problem.

    One thing though – for nudging to work it should move latitude not by 0.00017 (which is fine only at the map scale in this example), but by 1% of the span:

    newCenter.latitude = mapper.region.center.latitude – mapper.region.span.latitudeDelta/100;

    If you move by 0.00017 then in some zoom levels that might be quite visible, and in some zoom levels it might not move at all and not protect the clipping.

  54. Joerg | July 13, 2010 at 12:51 am | Permalink

    You might want to have a look at 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
    Hope this helps.

  55. Jassi | August 13, 2010 at 3:34 am | Permalink

    I am creating a line between two points on a map. The line is displayed but the problem is that whenever I am zooming the map , the app crashes. This happens in 8 GB iPhone and not on 16 GB. Is it a memory issue or what?

    I am submitting a snippet of code along with.
    Please help me.

    drawImage.frame = [tileContainerView frame];
    drawImage.tag = 555;
    UIGraphicsBeginImageContext(tileContainerView.frame.size);
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
    //CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 3.0);
    //CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
    CGContextBeginPath(UIGraphicsGetCurrentContext());
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), mapXFrom,mapYFrom+22);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(),(mapXFrom +mapXTo)/2,(mapYFrom+ mapYTo+22)/2);
    CGContextStrokePath(UIGraphicsGetCurrentContext());
    CGContextFlush(UIGraphicsGetCurrentContext());

    drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
    [self addSubview:drawImage];
    [self setUserInteractionEnabled:TRUE];
    UIGraphicsEndImageContext();

  56. McAndy | August 26, 2010 at 3:18 pm | Permalink

    I’m completely new to this topic and here’s my question: How do you download a route from google earth or google maps with longitudes and latitudes? – Is there a tool for that?

  57. Valerio | February 12, 2011 at 12:16 pm | Permalink

    Thank you Craig,
    It helped me a lot.
    I have a question. I’d like to draw a line on the map, then refreshing the view, by removing the old line and putting the new one with other points.
    How should I proceed?
    Thanks
    Valerio

  58. michael | April 15, 2011 at 2:33 pm | Permalink

    I have a problem using your implementation. The drawRect method in CSRouteView.m will not be called if you do the following:

    1) CSRouteAnnotation.m (initWithPoints method) – shift the _center coordinate:


    // the center point is the average of the max and mins
    _center.latitude = (minLat-1.5) + _span.latitudeDelta / 2;
    _center.longitude = (minLon-1.5) + _span.longitudeDelta / 2;

    This mimics placing points some distance outside of the view originally created.

    2) for visualizing, change the background color of CSRouteView from clear to gray in initWithFrame method.

    self.backgroundColor = [UIColor lightGrayColor];

    —–
    Now run the app. You will notice as you zoom/move the map around the gray background view doesn’t always consume the entire screen because of the frame constraints and probably functions as desired.
    Now pan over to the route location which is at its original coordinates (up and to the right of starting location). Zoom in so the route takes up the whole screen (from left to right). What you will notice is that the drawRect method is no longer called, the route disappears, the push pins remain, and the origin value calculated in CSRouteView.regionChanged is very different that what it was originally – probably what is causing drawRect to not be called by iOS.

    The problem seems to be that this implementation is only good for static points and is unable to handle points created some distance away from the views original frame.

    Any ideas on how to support this?

  59. joe | September 12, 2011 at 5:16 am | Permalink

    Hi michael, I’m experiencing the same problem. Did you manage to solve it? Would really appreciate if you could share.. I found that when the grey background disappears from the screen, the origin coordinates in regionChanged becomes a huge negative value.

    CGPoint origin = CGPointMake(0, 0);
    origin = [_mapView convertPoint:origin toView:self];

    It seems that the routeView did not update accordingly when the map is zoomed in/out or panned. Any help?

  60. rohith sreeram | February 20, 2012 at 2:25 am | Permalink

    i wanted to knw is there any method which automatically returns an array of (lattitude,longitude) between 2 points of a map, if this is available please let me know

  61. rohith sreeram | February 20, 2012 at 2:27 am | Permalink

    i wanted to know is there any method which will return an array of (lattitude,longitude) between to points of a map ??

  62. Stephen | March 30, 2012 at 2:05 pm | Permalink

    Hi, I am trying to get this code to work in an application, however I am very new to objective C, so I am not sure if I completely understand when drawRect is happening. I can’t find any calls to it so I believe some sort of event is triggering it, but it is never triggered in my application.

{ 8 } Trackbacks

  1. [...] 8-19-2009: Another update. This one displays the route as an annotation view, so it does not render on top of other annotations. You can view the update at this link. [...]

  2. [...] Update – 8/19/2009 Another update. This one displays the route as an annotation view, so it does not render on top of other annotations.You can view the update at this link. [...]

  3. [...] more googleing I found this site of Craig who actually proposed the some approach but had posted an update where he suggested to draw the route into a custom annotation-view to solve the problem with the [...]

  4. [...] solution, inspired by drawing polylines or routes on a MKMapView, has to do with adding a subview (TouchView see code in example) on top of the MKMapView and [...]

  5. [...] I should credit Craig for his example of drawing lines on a MKMapView (http://spitzkoff.com/craig/?p=108), that’s where you can find part 1 and part 2. This post is a kind of teaser what I’m [...]

  6. [...] App Store submissions later, if my app ever gets that far. I googled a bit and found Graig’s excellent blog post and sample project on how to draw polylines on a [...]

  7. [...] this blog post and sample code is an excellent guide to putting not only routes, but quite a lot of other things onto a MapKit [...]

  8. [...] have seen in this tutorial Drawing polyines or routes on a MKMapView that how to draw driving direction using core graphics…but in the sample code it has [...]

Post a Comment

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