Using MKAnnotation, MKPinAnnotationView and creating a custom MKAnnotationView in an MKMapView

My last experiment maps on the iPhone using the MKMapView from the iPhone’s MapKit was an example of how to use the MKMapView to display the line of a route on the map.

Today’s experiment will demonstrate how to drop pin annotation views, as well as custom annotation views on the iphone’s map by providing the map with objects that implement the MKAnnotation protocol.

The class I created that implements MKAnnotation can be used to differentiate between those annotations that should be represented by MKPinAnnotationViews of diferernt colors, as well as our custom annotation view, CSImageAnnotationView. The CSMapAnnotation class needs to keep track of additional information such as images and URLs that can be used for a custom display.

This is the header for the custom annotation. You can see that there is an enumeration that is used to designate what type of annotation view should be used for display of each annotation on the iPhone’s map. Also available are the userData and url properties; userData can be used to store anything, but in the case of this example, we’re using it to store path information for an image that should be displayed on the map. Title is also an available property, but subtitle will be generated on the fly.

//
// CSMapAnnotation.h
// mapLines
//
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
//

#import
#import

// types of annotations for which we will provide annotation views.
typedef enum {
CSMapAnnotationTypeStart = 0,
CSMapAnnotationTypeEnd = 1,
CSMapAnnotationTypeImage = 2
} CSMapAnnotationType;

@interface CSMapAnnotation : NSObject
{
CLLocationCoordinate2D _coordinate;
CSMapAnnotationType _annotationType;
NSString* _title;
NSString* _subtitle;
NSString* _userData;
NSURL* _url;
}

-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate
annotationType:(CSMapAnnotationType) annotationType
title:(NSString*)title;

@property CSMapAnnotationType annotationType;
@property (nonatomic, retain) NSString* userData;
@property (nonatomic, retain) NSURL* url;

@end

In most cases, when our MKMapViewDelegate is asked for an MKAnnotation, we return a MKPinAnnotation. If the CSMapAnnotation is set to be CSMapAnnotationTypeImage , the delegate instead produces an instance of our custom annotation view, CSImageAnnotationView, and sets its properties based on the annotation’s user data.

This is the body of our custom annotation view. It is pretty simple; the bulk of the functionality is in the initialization function, which sets up the view to have a UIImageView.

//
// CSImageAnnotationView.m
// mapLines
//
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
//

#import "CSImageAnnotationView.h"
#import "CSMapAnnotation.h"

#define kHeight 100
#define kWidth 100
#define kBorder 2

@implementation CSImageAnnotationView
@synthesize imageView = _imageView;

- (id)initWithAnnotation:(id )annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
self.frame = CGRectMake(0, 0, kWidth, kHeight);
self.backgroundColor = [UIColor whiteColor];

CSMapAnnotation* csAnnotation = (CSMapAnnotation*)annotation;

UIImage* image = [UIImage imageNamed:csAnnotation.userData];
_imageView = [[UIImageView alloc] initWithImage:image];

_imageView.frame = CGRectMake(kBorder, kBorder, kWidth - 2 * kBorder, kWidth - 2 * kBorder);
[self addSubview:_imageView];

return self;

}

-(void) dealloc
{
[_imageView release];
[super dealloc];
}

@end

So, how does the map know to ask the delegate for annotation views? It knows to do this based on annotations that are added to the map in our view controller’s viewDidLoad method.


// create the image annotation
annotation = [[[CSMapAnnotation alloc] initWithCoordinate:[[points objectAtIndex:points.count / 2] coordinate]
annotationType:CSMapAnnotationTypeImage
title:@"Cleveland Circle"] autorelease];
[annotation setUserData:@"cc.jpg"];
[annotation setUrl:[NSURL URLWithString:@"http://en.m.wikipedia.org/wiki/Cleveland_Circle"]];

[_mapView addAnnotation:annotation];

UPDATE – 6/1/2009
You’ll notice we set the URL on the annotation above, and the code has now been updated to display that webpage when the user clicks the annotation disclosure of our custom image annotation view. The code sample you can download has been updated to reflect this change.

The full code of this example is available for download here.

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.

Tags: , ,

46 Responses to “Using MKAnnotation, MKPinAnnotationView and creating a custom MKAnnotationView in an MKMapView”

  1. The Reluctant Blogger :: Drawing polyines or routes on a MKMapView (Using Map Kit on the iPhone) Says:

    [...] I have an updated post here that builds on this project and also discusses using Using MKAnnotation,… [...]

  2. jf Says:

    Great stuff. Really gets me started on Mapkit.

    I was wondering: Can we use bits of this code without restriction?

  3. admin Says:

    Yup.. use it however you want. I’m glad you found it useful.

  4. Bernard Says:

    If your CSImageAnnotationView is also a descendant of the UIControl class, you can use the map view’s delegate to receive notifications when your control is tapped.

  5. jf Says:

    Thanks much.

    Another question: I noticed when I zoom in, the lines don’t seem to stay at the same scale as the map (lines get off the pixel points slightly). Do you see that, or is it something with my build perhaps?

  6. jf Says:

    Did you ever get the calloutaccessory button to trigger?

  7. ed Says:

    Yes, I have also had no luck with the callout responding. Hopefully someone has figured it out. Still working on it! I am thinking there is some invisible layer? Sitting on top of it. Not sure, but have tried a zillion ideas so far.

  8. admin Says:

    I just figured out how to get the calloutAccessoryControlTapped to trigger, wit thanks to ed’s comment about the “invisible layer”. Thats exactly waht the problem was, that our route layer was intercepting the click. The addition of one line of code in the viewDidLoad function on the CSMapRouteLayerView prevents the view from intercepting UI events. That line is:

    [self setUserInteractionEnabled:NO];

    I’ll upload an updated example in a few minutes that has the fix.

    Thanks for your help!

  9. ed Says:

    Wow, cool fix. thanks!

    How did you master this stuff? It’s a lot of work I think. Is there ONE book you would take to a desert island explaining the ins and outs of ObjC/Cocoa? I’ll go out and get it.

    I’m playing with dropping zillions of pins on a map at once, it’s very crazy! I call it love bombing (want to replace the pins with hearts and peace symbols).

    Friends think I’m crazy, but just love the Mapkit.

    thanks! :-) ed

  10. jf Says:

    Too cool. I was trying to resignfirstresponder, forward touches, etc. since I figured it had to be the map overlay, but of course it was so much simpler than that! (I really have to learn to code more elegantly!)

    Thanks for your support to the community.

  11. admin Says:

    No problem… glad I could help. I’ve updated the attached code sample to actually do something now that the calloutAccessoryControlTapped gets triggered.

  12. Scott Says:

    Thanks, I’m just getting started w/MKMapKit… and this is the first code I’ve seen that actually works. Well, until I break it, of course. :)

  13. Zaplitny Says:

    Has anybody got success in customizing calloutView. The problem is that height of calloutView is fixed and adding view with height greater than callout view’s makes UI ugly

  14. Lee Armstrong Says:

    No matter how I try and insert this into my project I cannot get the lines to appear!!!! Grr!

    Any ideas?

    All I want to do in essence is draw a single straight line at co-ords 0,0 as a test and can’t even do that.

    Can you help please for a numpty like me?

  15. admin Says:

    Lee… send me some code; I’ll be happy to take a look at it.

  16. Lee Armstrong Says:

    Thanks,

    I actually just had a good crack at it and now have your points loaded onto my mapView! Great!

    All of the pins that I had (custom images) are now all red pushpins…also the callouts have gone :-(

    I’d rather not send some code over but any pointers welcome…

  17. Sanjay Says:

    Really many Many Thank you.
    May god bless you

  18. ck Says:

    Thanks! Is there any way to make the line appear below the annotation views?

  19. Bob Says:

    Looks like HTML ate part of the #import lines ‘cuz of the angle brackets. Might wanna replace those with < and >

  20. Slava Says:

    Thanks for the saples.
    Please help, how to animate and move such imaged markers?

  21. Puppet Says:

    First of all thank you very much for sharing this code!!!

    I too would like to know how to get the line to be below the annotations also. I’ve tried creating a 3rd layer like the CSMapRouteLayerView with an empty drawRect and adding annotations to it, but much to my dismay, no joy…

  22. Sebastian Says:

    Thank you a lot for your great examples.
    It answered all my questions and i am happily adding lots
    of great maps to my application right now!

  23. jissa Says:

    hi, thanks for this tutorial ,but i have question:
    is there any code to make the pin(MKPinAnnotationView) move when the user moved or touch?

  24. Ross Kimes Says:

    What is the easiest way to display and image that will resize itself over the map instead of the line. My plan was to use your example, but to use an UIImageView instead of an UIView, but an UIImageView does not call -drawRect.

    Any ideas on how to get this done? Thanks!

  25. Ross Kimes Says:

    Never mind I figured out image problem, but I would like to echo the question of how to get the lines below the annotations (in this case a pin annotation).

    Thanks!

  26. Ed Scott Says:

    Craig this is a GREAT little app to teach some things and helped me out more than any other to learn….Thanks!

    I do have a question, is there a way we can get the blue line to appear under the annotation? Often it crosses over my annotation making it hard to read.

  27. Seb Says:

    Hi, thank you for this great tutorial. It works perfectly into my project, but I have one problem :
    I have a NSTimer which refresh the imageAnnotation pin. The first time it works, but for the second, the images becomes red pins.

    What’s wrong ? Thank you for your help.

  28. Shawn Says:

    Thanks for the great demo, I have a question and a problem though:
    You example uses and table of points for the lines, How would you determine the points between any two Lat/Lon ? (IE display a route line between Current position and another annotation on the map)

    Secondly, I can not seem to get the rightCalloutAccessoryView to work
    [pin setCanShowCallout:YES];
    pin.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];

    Callout shows up, but the delegate function never gets called when button hit
    - (void)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation calloutAccessoryControlTapped:(UIControl *)control
    {NSLog(“@button pushed”);

  29. wanna Says:

    Thank you for the blog.
    I got questions regarding to removeAnnotation and dequeueReusableAnnotationViewWithIdentifier.

    I have the following code to remove all the pins from the map.
    -(IBAction)removeAllAnnotationTypeFromMap:(id)sender
    {
    NSArray *annotationArrs = self.mapView.annotations;
    if(annotationArrs!=nil)
    {
    NSLog(@”Remove all annotations!”);
    [self.mapView removeAnnotations:annotationArrs];
    }
    }

    1/how do I check if there is no pin on the map then add all the pins back to the map?

    2/how do I remove and later deque a specific group of annotation back to the map?

    Thank you for your help.

  30. jissa Says:

    Thanks for the samples.
    is there any code to make the pin(MKPinAnnotationView) move when the user drag or touch?

  31. jassous Says:

    thanks for this example.
    my application will determine the distance between 2 pin that the user will putted. i put a pin on the map but i can move it ,i try pin.userintersectionenabled=yes + -(void)toucheBegan: but it’s not working can you help me?please?

  32. Drawing polyines or routes on a MKMapView (Using Map Kit on the iPhone) « The Reluctant Blogger Says:

    [...] I have an updated post here that builds on this project and also discusses using Using MKAnnotation,… [...]

  33. Ed Wrenbeck Says:

    Thanks for the post Craig, like others it really helped me get started with the map view stuff.

    I was wondering why you didn’t implement the route as an annotation view, rather than as a separate view sitting on top of the map?

    I was running into the problem where the map line was being drawn over my pin and my pop up, which it looks like your sample code has the same.

    I reimplemented the route view to be an annotation view and now it sits nicely behind the pins and pop ups.

    The other benefit of doing it this way is that when the user is scrolling the map, the route can stay on screen and go with it. So now my only problem is when the map is being scaled the lines gets misplaced until the scaling is done. Now I’m still debating whether to hide or show the line when moving/scaling. Ideally of course, I’d like to figure out how to fix the scaling too.

    I haven’t decided if its just a bug in my code or not, which it probably is, but it seems like it stops drawing the annotation view with the route about 25-30 pixels from the top of the screen.

    I want to post my code, but its just too dirty right now, but hopefully I’ll get it whipped into shape.

    Thanks again, Ed

  34. Lars Says:

    Hey I have a quick question. Im using your code and i modified a bit to draw a line of where the person is. the problem im having is that i get this error:
    Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Invalid Region ‘

    Why is it doing this? is it because of zoom? because that point does exist?
    any ideas?

    Thanks
    -Lars

  35. admin Says:

    Thanks for the comment Ed. I liked the idea you proposed of drawing the lines in an annotation view instead of as an overlaid view on the map, so I updated the code to do just that. Its somewhat more complicated due to the way a map view will move the annotation view offscreen and the size of the route actually being larger than the view itself, but I think I’ve gotten around that using a non-clipped subview of the annotation view that always stays at 0,0 relative to the map view.

    This will address other people’s concerns about drawing the line under other annotations instead of drawing on top of them.

    The updated code is here: http://spitzkoff.com/craig/?p=108

  36. Chris Says:

    Hi,

    great tutorial!, i have a simiar problem as seb, i’ve setup a timer to change the image and position of a annotationview, but can’t get it working properly,
    the stuff gets set but never results in a screen refresh.

    used your cvs file to create a bunch of markers and then:

    - (void) updateView:(NSTimer*)theTimer {

    for (CSMapAnnotation* csAnnotation in [_mapView annotations] ) {
    CSImageAnnotationView* imageAnnotationView = (CSImageAnnotationView*) [self mapView:_mapView viewForAnnotation:csAnnotation];
    if (blue) {
    NSLog(@”switching to blue”);
    [imageAnnotationView updateImage:@"blue_0.png"];
    }
    else {
    NSLog(@”switching to red”);
    [imageAnnotationView updateImage:@"red.png"];
    }
    }
    if (blue) {
    blue = NO;
    }
    else {
    blue = YES;
    }
    [_mapView setNeedsDisplay];

    where update image is:

    - (void) updateImage:(NSString*) name {
    //NSLog(@”CSImageAnnotationView.updateUI”);
    [_imageView setImage:[UIImage imageNamed:name]];
    [_imageView setNeedsDisplay];
    }

    any idea?

    thanks

    Chris

  37. admin Says:

    Hi Chris,

    I’m not sure (I haven’t tested your specific code snippet) but performing the updateImage method on the main thread may help. So instead of calling [imageAnnotationView updateImage:...] you could try [imageAnnotationView performSelectorOnMainThread:@selector(updateImate:) withObject:...];

    Please let me know if that helps.

    -Craig

  38. River Tiger Says:

    Craig, I was curious as to whether its possible to extend CSMapAnnotation to have a bigger popup view…not just only to include a title, subtitle, URL, accessory chevron, but also a bigger content window…basically a larger view with more content….does MKAnnotation smart enough to scale that view window to be bigger?

  39. Jeremy Says:

    Hi Craig,

    This is such a nice example, in fact best I could find, it makes it easier to understand than reading Apple’s documents.

    Just a quick question.. I am trying to draw a little car icon, do you know if it is possible to rotate the image such as your CC.jpg from 0-360 degrees? Also does the image support transparency because I want to show it as an icon and not an fully opaque picture.

    Thanks in advance

  40. kish Says:

    Hi Craig,

    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

  41. Lomiunefeme Says:

    polycosanol tea Order Levlen effect of particle in insulating mineral oil mental health relating to alcahol arizona asthma and allergy scottsdale
    http://rxdrugs24×7.com/category/anti-viral.html

  42. tnathos Says:

    hi craig.

    thanks for share the code.. i have a probke y using title and subtilte but i cant autoajust the bubble so if i have a long string, the bubble show me aaaaaa…..

    how i can modify this? or how i can make a custom bubble?

  43. Stephen Says:

    Craig,
    Excellent tutorial and did quite a lot of what I wanted it to do out of the box.

    I too have a couple of questions! I implemented the route in an annotationView but my some of my point annotations are under and some lie over the route line. Any idea how this might happen? I need to overlay annotations on top of others and it would be great to be able to control the order.

    Looking at a previous post from Ed I too have a problem displaying the route at the top of the screen. The first time it is drawn it clips the top about 25 pixels from the screen edge. After scrolling however it draws fine even up to the edge.

    Many thanks

  44. Stephen Says:

    All,
    Apologies! I just read the solution on the other blog entry and all is solved on the overlaid annotations!

    The clipping of the route at the top of the screen is still an issue though if anyone has any pointers!

    Regards

  45. Gordon McDowell Says:

    Like Stephen, I’m also seeing clipping as I zoom in and out of lines. I made suggested change so lines refresh as zoom in & out, which makes it less visible… sort of flickers so user can keep moving until line visible… but if there’s a fix specific to that here I’m overlooking it.

    Also… Craig, it is not obvious to me why you track routes with a data dictionary instead of using the dequeueReusableAnnotationViewWithIdentifier like the images and pins? I mean in MapLinesViewController … – (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation

  46. admin Says:

    Gordon,

    Please see the updated entry at:
    http://spitzkoff.com/craig/?p=108

Leave a Reply