Custom callout bubble in MKMapView, final solution!

Once again, this post has been updated.

I was not completely satisfied with my prior solution to custom callout bubble in MKMapView due a drawback.

One drawback is that if you click an annotation in the TouchView it is not propagated down to the MKMapView which makes pinch zoom bit more tricky if you have many annotations. Someone out there might have a good solution for it?

Fortunately I figured out a new solution for the problem! A much more simple solution too.

It is a combination of the property change listener solution and moving the calloutOffset off the display.

Set the calloutOffset off the display and add an observer to the selected-property.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation {

MKAnnotationView* annotationView = nil;

MyAnnotation *myAnnotation = (MyAnnotation*) annotation;
NSString* identifier = @"Pin";
MKPinAnnotationView* annView = (MKPinAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];

if(nil == annView) {
annView = [[[MKPinAnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:identifier] autorelease];
}
//Add an observer for the selected-property on the MKAnnotationView. Delegate to self.
[annView addObserver:self
forKeyPath:@"selected"
options:NSKeyValueObservingOptionNew
context:GMAP_ANNOTATION_SELECTED];

[annView setPinColor:MKPinAnnotationColorGreen];

//Set calloutOffset off screen.
CGPoint notNear = CGPointMake(10000.0,10000.0);
annView.calloutOffset = notNear;
annotationView = annView;

[annotationView setEnabled:YES];
[annotationView setCanShowCallout:YES];

return annotationView;
}

Implement the observeValueForKeyPath method. It will be triggered when the property is selected or deselected.

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context{

NSString *action = (NSString*)context;

if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){
BOOL annotationAppeared = [[change valueForKey:@"new"] boolValue];
if (annotationAppeared) {
[self showAnnotation:((MyAnnotationView*) object).annotation];
}
else {
NSLog(@"annotation deselected %@", ((MyAnnotationView*) object).annotation.title);
[self hideAnnotation];
}
}
}

Screenshot_17

Take a look at my new example (with standard MKPinAnnotationView) or with custom annotation view.

Be Sociable, Share!

43 thoughts on “Custom callout bubble in MKMapView, final solution!

  1. Pingback: Custom callout bubble to MKMapView in iPhone « JAKERI

  2. admin Post author

    The custom callout is designed in Interface Builder. You may design this however you want. Add detailed information to your custom MKAnnotation class. Did you test the example?

    Not completely sure what you mean.

  3. Craig

    I tested your example. Very nice. I have been struggling for some time on how to pass additional properties to a call-out bubble such as phone, zip, notes, etc. The solution continues to elude me.

  4. admin Post author

    Method observeValueForKeyPath ofObject is the surrounding annotation view. Calling annotationView.annotation will get your custom MKAnnotation implementation.

    See code in example.

    - (void)observeValueForKeyPath:(NSString *)keyPath
    ofObject:(id)object
    change:(NSDictionary *)change
    context:(void *)context{

    NSString *action = (NSString*)context;

    if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){
    BOOL annotationAppeared = [[change valueForKey:@"new"] boolValue];
    if (annotationAppeared) {
    NSLog(@"annotation selected %@", ((MyAnnotationView*) object).annotation.title);
    [self showAnnotation:((MyAnnotationView*) object).annotation];
    }
    else {
    NSLog(@"annotation deselected %@", ((MyAnnotationView*) object).annotation.title);
    [self hideAnnotation];
    }
    }
    }

  5. Craig

    no, I got that part. But all the annotation images are identical. It is impossible to tell “which” annotation is the selected one.

  6. admin Post author

    Have not tested that. I solved this issue by moving map region to the center of the display when you click on it.

    It should probably be solvable with a layer above the mapview where you draw something around the annotation.

    It might also be possible to replace the image (MKAnnotationView.image) in the showAnnotation method in the code above. I have not tested it.

  7. admin Post author

    It works. See below. Added a change to MKAnnotationView.image in th observeValueForKeyPath.


    - (void)observeValueForKeyPath:(NSString *)keyPath
    ofObject:(id)object
    change:(NSDictionary *)change
    context:(void *)context{

    NSString *action = (NSString*)context;

    if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){
    BOOL annotationAppeared = [[change valueForKey:@"new"] boolValue];
    if (annotationAppeared) {
    NSLog(@"annotation selected %@", ((MyAnnotationView*) object).annotation.title);
    [self showAnnotation:((MyAnnotationView*) object).annotation];
    ((MyAnnotationView*) object).image = [UIImage imageNamed:@"icon-sel.png"];
    }
    else {
    NSLog(@"annotation deselected %@", ((MyAnnotationView*) object).annotation.title);
    [self hideAnnotation];
    ((MyAnnotationView*) object).image = [UIImage imageNamed:@"icon.png"];
    }
    }
    }

  8. Craig

    That worked perfectly. I even added code to (showAnnotation) to center the annotation when clicked.

    I have a new question: suppose you wanted to show an “expanded” detail view linked from the small annotation view that pops up. I added a “more info” button to the small annotation overlay and linked to a new View Controller. But how do you transfer the annotation data to the next view controller?

    I have not seen another post out there like this one. It is very useful.

  9. Matthew

    Wondering how you might be able to customize the pins. Standard customization does not seem to be working for me.

    Also, wondering like @Craig, above, how you might push a new view controller from the annotation view.

    tia

  10. admin Post author

    Sorry for my late answer.

    Just pass a reference to your MKAnnotation (set a property) containing information to the newly created view controller before you present it.

    I am not sure I understand what you mean by customize the pins? use another image?
    Then follow standard code from apple using a custom MKAnnotationView.

  11. Matthew

    I have tried countless ways at this point to change the pin, which works in my standard maps, but does not seem to do anything to change the pins in this demo.

    It is a great technique – thanks for sharing.

  12. Matthew

    Do you have any other leads on learning more about KVO in relation to mapkit; pulling detailed information and passing it onto other views? Not really gleaning that from the Apple docs – perhaps it is just me :)

  13. Sijo

    Thanks for this great post.. i just need to add more details in MKPinAnnoataionView like Title,Address,Description,date, an image etc.. But its View should look like default MKPinannotation. I need that default black transparent bubble with the details…can we do anything in default MKPinannoation ? something like rightCalloutAccessoryView or leftCalloutAccessoryView ? .. but when am using this it getting located at left or Right side of PIn..thanks in advance

  14. John

    I did not see any code to forward pinch-zoom event from your TouchView to your map view. We’re you able to get pinch zoom to work on your TouchView, so that you can zoom your MKMapView? Thanks

  15. admin Post author

    Yes. Everything works as the built in solution.
    TouchView is only used to catch deselection of tbe custom annotation. The original annotationview is actually visible if you have a really large iphone screen.

    CGPoint notNear = CGPointMake(10000.0,10000.0);
    self.calloutOffset = notNear;

  16. John

    Cool. My soln almost works. I’ll adapt to yours and see how that works out. Thanks for sharing.

  17. Chris

    When i try this with OS4 the map View goes crazy to the point 10000.0,10000.0 where the original annotationview is.

    It only works when i try this:
    CGPoint notNear = CGPointMake(1.0,1.0);
    self.calloutOffset = notNear;

    But then i have both annonationviews on screen.

    Do you have any solution for this? I only have an iPod touch to test and i can’t go back from beta.

  18. admin Post author

    I have not have the time to test in OS4 yet. If you have any solution please mail me.
    OS4 is still in NDA so I can not say to much.

  19. Lifey

    There seems to be a problem with your solution on iPhoneOS3 simulator (and therefore most likely on the iPhoneOS3 device, though I haven’t checked): If the annotation is held for around a second or two, it zooms to the location of the callout bubble (which in your example is quite far away!) Do you have any ideas on how to remedy this issue?

  20. admin Post author

    I think I have found a solution working for iPhoneOS 3.0, 3.1, 3.2 and 4.0. Both iPad and iPhone. Will try to make new post in a couple of days.

  21. dc

    Have you any idea of a way of releasing the observationInfo so that it does not leak when an annotation is removed?

    Much thanks for the sample code.

  22. adam

    I love your solution for this, elegant and cunning.

    Really disappointed about the news that it’s not working on OS 4 beta, though. I guessed that might happen (Apple often does things like this – if you’ve got something visible, sooner or later they “helpfully” change the system behaviour to show it).

    Did you find a new solution (version 3 of this fix, I guess :) ) ?

  23. admin Post author

    I think so. Will try to post tomorrow. Only thing I have not fixed is the leaking of observationinfo.

  24. Pingback: Revisited – Custom callout bubble in MKMapView, final solution! « JAKERI

  25. Devin Foley

    I was able to get this working in iOS4 by commenting out the notNear stuff and setting canShowCallout to NO:

    //CGPoint notNear = CGPointMake(10000.0,10000.0);
    //annView.calloutOffset = notNear;

    [annotationView setCanShowCallout:NO];

  26. Malek

    Hi, please, have you any tutorial which shows how to move data from a view to another to show them when i select the UIButtonTypeDetailDisclosure button of the pin ? my purpose is that when i click the detail button on the pin, i will be redirect to another view in which i place all details of the pin selected, please help, i really need step-by step tutorial,i have maked the pins with the detail button, and the data are retrieved from web-service, but i don’t know how to tell view “here the data of this pin, display it”.

  27. ambu.sangoli

    2011-05-22 01:14:43.415[6106:307] stopFollowLocation called. Good place to put stop follow location annotation code.
    2011-05-22 01:14:43.420[6106:307] stopFollowLocation called. Good place to put stop follow location annotation code.
    2011-05-22 01:14:43.424[6106:307] stopFollowLocation called. Good place to put stop follow location annotation code.
    2011-05-22 01:14:43.885[6106:307] annotation deselected (null)

    Please help i am getting all working but not showing any text and in console the above it is showing

  28. Simon Burfield

    Hello there, I downloaded by examples and ran it up on 4.2 in simulator and it does some very odd things when you click the pin and I end up with different views show?

  29. Pingback: Custom popup when clicking pin in MKMapView | Software development support, software risk,bugs for bugs, risk analysis,

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>