33

Here is my code. I want to add my own custom callout view instead of iOS default. I know there is only left callout and right callout view, but I need to add a view tooltip type with my own background and label.

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
    {
        MKAnnotationView *userAnnotationView = nil;
        if ([annotation isKindOfClass:MKUserLocation.class])
        {
            userAnnotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:@"UserLocation"];
            if (userAnnotationView == nil)  {
            userAnnotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"UserLocation"];
            }
            else
                userAnnotationView.annotation = annotation;

            userAnnotationView.enabled = YES;


            userAnnotationView.canShowCallout = YES;
            userAnnotationView.image = [UIImage imageNamed:@"map_pin.png"];
            UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0,0,141,108)];
            view.backgroundColor = [UIColor clearColor];
            UIImageView *imgView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"toltip.png"]];
            [view addSubview:imgView];
            userAnnotationView.leftCalloutAccessoryView = view;



            return userAnnotationView;
        }

}

Image for reference

Arnaud
  • 7,259
  • 10
  • 50
  • 71
Gaurav
  • 451
  • 1
  • 5
  • 7

6 Answers6

133

I had same problem and ended up doing following thing:

When I receive mapView delegate callback

-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view

(it's time when I want to show my custom CalloutView)

I use view received as parameter *(MKAnnotationView )view (which is a pin view) and simply add my custom view to that pin view using

 [view addSubview:customView];

It will add your custom view on top of that pin view so if we want it to be above pin we have to change custom view center property like this:

CGRect customViewRect = customView.frame;
        CGRect rect = CGRectMake(-customViewRect.size.width/2, -customViewRect.size.height-7, customViewRect.size.width, customViewRect.size.height);
        customView.frame = rect;
[view addSubview:customView];

In my case it looks like this

enter image description here

!NOTE:

There is one caveat which however can be easily fixed, your custom view will ignore touch events, because of mapView working with touches differently. Here's a quick fix:

1) Subclass MKAnnotationView (or MKPinAnnotationView depends on what you need)

2) in your mapView delegate

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation

use your subclass instead of MKAnnotationView/MKPinAnnotationView

3) in your subclass .m file override two methods as following:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* hitView = [super hitTest:point withEvent:event];
    if (hitView != nil)
    {
        [self.superview bringSubviewToFront:self];
    }
    return hitView;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
{
    CGRect rect = self.bounds;
    BOOL isInside = CGRectContainsPoint(rect, point);
    if(!isInside)
    {
        for (UIView *view in self.subviews)
        {
            isInside = CGRectContainsPoint(view.frame, point);
            if(isInside)
                break;
        }
    }
    return isInside;
}

All done!

Pedro Romão
  • 2,285
  • 28
  • 22
haawa
  • 3,078
  • 1
  • 26
  • 35
  • 11
    what is visibleCalloutView ? – Kunal Balani May 18 '14 at 18:46
  • Dunno if there are any better ways of doing this, but one year later, this solution works amazingly well! Thanks for the additions on working out the touch issues with the custom view. Cheers! – Madhu Jul 01 '14 at 12:20
  • Regarding touch issues, the touch works, but didDeselectAnnotationView still gets called and it dismisses the callout. Do you have the same? Is there a more elegant solution than flagging the 'touched' annotation view to save its state and prevent deselection? – Christophe Fondacci Jul 21 '14 at 09:58
  • @ChristopheFondacci is didDeselectAnnotationView get called when you touch annotationView or customView, that you added? – haawa Jul 21 '14 at 11:04
  • @haawa deselect called when I touch the custom view. I managed to get the custom view receive the hit, but it is as if the event is propagated to the underlying map. For example, I can drag the map while touching and holding on the custom view, or if I tap my custom view and there is another annotation behind, this other annotation is selected. I got the impression that whenever the tap is outside the annotation view bounds, you could manage to receive the hit, but can't avoid the map to process it as well – Christophe Fondacci Jul 21 '14 at 23:17
  • just wanted to add that to make this view disappear on button click, add MKMapView and id as properties of this custom view and call **[self.mapView deselectAnnotation:self.annotation animated:YES];**. MKAnnotation is a property of MKAnnotationView. – Yasmin Tiomkin Aug 07 '14 at 09:02
  • @amcastror not sure why, but you need to have a UIButton as the control that receives the touch outside annotation bounds and override hitTest in the AnnotationView to check for hits in subviews (something like this answer but I recursively hitTest subview). When UIButton receives the hit, it works (but it is slow), without UIButton the touch reaches the map but it is very fast... I think there is a bug somewhere in MapKit – Christophe Fondacci Nov 18 '14 at 21:51
  • Hi haawa, can you please share your custome view code, i am new to mapkit, it will save me alot thanks! – Gamer May 14 '15 at 10:41
  • 2
    addSubview:customView.center = CGPointMake(view.bounds.size.width*0.5f, -self.visibleCalloutView.bounds.size.height*0.5f); In this method "visibleCalloutView " which object repesents, i m getting error "not found on object type ", please help me. – Gamer May 14 '15 at 14:04
  • did you guys managed to do it? I have the same problem here and I don't understand what "you need to have a UIButton as the control that receives the touch outside annotation bounds and override hitTest in the AnnotationView to check for hits in subviews" means – Andrei Dobrin May 20 '15 at 12:43
  • @Gamer this shows erroe, did you find any solution? addSubview:customView.center = CGPointMake(view.bounds.size.width*0.5f, -self.visibleCalloutView.bounds.size.height*0.5f); – Niks May 27 '15 at 10:50
  • Awesome post! I think visibleCalloutView is the customView in the example from @haawa. – ssb Jul 08 '15 at 04:43
  • @haawa - Salute you boss, save my life can you please elaborate more why MKAnnotationView ignore touches ? – iPatel Jul 26 '16 at 11:21
  • 2
    @iPatel it is because the added subview is out of superview's visible rect. if you set `clipsToBounds` on `MKAnnotationView`, you can't even see the added subview. From iOS9, there is a `detailCalloutAccessoryView` to customize callout. – Ryan Aug 19 '16 at 21:33
  • What is VisibleCallOutView?? – Mohammed Hussain Aug 31 '16 at 10:13
  • I'm confused here - adding the subview will add your custom view **below** the callout? How can you move your custom view above the callout? OR in reference to my question http://stackoverflow.com/questions/43766219/remove-mkannotationview-default-callout-from-view – Will Von Ullrich May 03 '17 at 17:14
10

I have created a library to show custom callouts.

DXCustomCallout-ObjC

You can pass any custom view for the callout! It supports events for UIControls as well.

Custom Callout

Community
  • 1
  • 1
Selvin
  • 12,333
  • 17
  • 59
  • 80
2

To the best answer above in Swift

This can save some minutes to rewrite itself.

!NOTE:

There is one caveat which however can be easily fixed, your custom view will ignore touch events, because of mapView working with touches differently. Here's a quick fix:

1) Subclass MKAnnotationView (or MKPinAnnotationView depends on what you need)

2) in your mapView delegate

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView?

use your subclass instead of MKAnnotationView/MKPinAnnotationView

3) in your subclass .m file override two methods as following:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    let hitView = super.hitTest(point, withEvent: event)

    if (hitView != nil)
    {
        self.superview?.bringSubviewToFront(self)
    }

    return hitView;

}

override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {

    let rect = self.bounds
    var isInside = CGRectContainsPoint(rect, point)

    if(!isInside) {
        for view in self.subviews {

            isInside = CGRectContainsPoint(view.frame, point)
            break;
        }
    }

    return isInside
}
1

I just created a custom callout for my map view which avoids issues with the callout disappearing when tapped. Here is a Gist with the steps I took.

Jagat Dave
  • 1,643
  • 3
  • 23
  • 30
bryguy1300
  • 901
  • 9
  • 11
0

You can use this library. It also provides a ready-to-use template for the callout view. Adds cool animations and an anchor view for your custom views.

https://github.com/okhanokbay/MapViewPlus

enter image description here enter image description here

Okhan Okbay
  • 1,374
  • 12
  • 26