ios - detailcalloutaccessoryview - swift mkannotation callout

How to customize the callout bubble for MKAnnotationView? (6)


In the olden days this was a pain, but Apple has solved it, just check the docs on MKAnnotationView

view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.detailCalloutAccessoryView = UIImageView(image: UIImage(named: "zebra"))

Really, that's it. Takes any UIView.

I'm currently working with the mapkit and am stuck.

I have a custom annotation view I am using, and I want to use the image property to display the point on the map with my own icon. I have this working fine. But what I would also like to do is to override the default callout view (the bubble that shows up with the title/subtitle when the annotation icon is touched). I want to be able to control the callout itself: the mapkit only provides access to the left and right ancillary callout views, but no way to provide a custom view for the callout bubble, or to give it zero size, or anything else.

My idea was to override selectAnnotation/deselectAnnotation in my MKMapViewDelegate, and then draw my own custom view by making a call to my custom annotation view. This works, but only when canShowCallout is set to YES in my custom annotation view class. These methods are NOT called if I have this set to NO (which is what I want, so that the default callout bubble is not drawn). So I have no way of knowing if the user touched on my point on the map (selected it) or touched a point that is not part of my annotation views (delected it) without having the default callout bubble view show up.

I tried going down a different path and just handling all touch events myself in the map, and I can't seem to get this working. I read other posts related to catching touch events in the map view, but they aren't exactly what I want. Is there a way to dig into the map view to remove the callout bubble before drawing? I'm at a loss.

Any suggestions? Am I missing something obvious?

Basically to solve this, one needs to: a) Prevent the default callout bubble from coming up. b) Figure out which annotation was clicked.

I was able to achieve these by: a) setting canShowCallout to NO b) subclassing, MKPinAnnotationView and overriding the touchesBegan and touchesEnd methods.

Note: You need to handle the touch events for the MKAnnotationView and not MKMapView

Found this to be the best solution for me. You'll have to use some creativity to do your own customizations

In your MKAnnotationView subclass, you can use

- (void)didAddSubview:(UIView *)subview{
    int image = 0;
    int labelcount = 0;
    if ([[[subview class] description] isEqualToString:@"UICalloutView"]) {
        for (UIView *subsubView in subview.subviews) {
            if ([subsubView class] == [UIImageView class]) {
                UIImageView *imageView = ((UIImageView *)subsubView);
                switch (image) {
                    case 0:
                        [imageView setImage:[UIImage imageNamed:@"map_left"]];
                    case 1:
                        [imageView setImage:[UIImage imageNamed:@"map_right"]];
                    case 3:
                        [imageView setImage:[UIImage imageNamed:@"map_arrow"]];
                        [imageView setImage:[UIImage imageNamed:@"map_mid"]];
            }else if ([subsubView class] == [UILabel class]) {
                UILabel *labelView = ((UILabel *)subsubView);
                switch (labelcount) {
                    case 0:
                        labelView.textColor = [UIColor blackColor];
                    case 1:
                        labelView.textColor = [UIColor lightGrayColor];

                labelView.shadowOffset = CGSizeMake(0, 0);
                [labelView sizeToFit];

And if the subview is a UICalloutView, then you can screw around with it, and what's inside it.

I had the same problem. There is a serious of blog posts about this topic on this blog

Just using the MKMapViewDelegate doesn't help you here and subclassing MKMapView and trying to extend the existing functionality also didn't work for me.

What I ended up doing is to create my own CustomCalloutView that I am having on top of my MKMapView. You can style this view in any way you want.

My CustomCalloutView has a method similar to this one:

- (void) openForAnnotation: (id)anAnnotation
    self.annotation = anAnnotation;
    // remove from view
    [self removeFromSuperview];

    titleLabel.text = self.annotation.title;

    [self updateSubviews];
    [self updateSpeechBubble];

    [self.mapView addSubview: self];

It takes an MKAnnotation object and sets its own title, afterward it calls two other methods which are quite ugly which adjust the width and size of the callout contents and afterward draw the speech bubble around it at the correct position.

Finally the view is added as a subview to the mapView. The problem with this solution is that it is hard to keep the callout at the correct position when the map view is scrolled. I am just hiding the callout in the map views delegate method on a region change to solve this problem.

It took some time to solve all those problems, but now the callout almost behaves like the official one, but I have it in my own style.

I've pushed out my fork of the excellent SMCalloutView that solves the issue with providing a custom view for callouts and allowing flexible widths/heights pretty painlessly. Still some quirks to work out, but it's pretty functional so far:

There is an even easier solution.

Create a custom UIView (for your callout).

Then create a subclass of MKAnnotationView and override setSelected as follows:

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
    [super setSelected:selected animated:animated];

        //Add your custom view to self...
        //Remove your custom view...

Boom, job done.