Skip to content

Instantly share code, notes, and snippets.

@ShadoFlameX
Last active May 20, 2018 14:14
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save ShadoFlameX/7495098 to your computer and use it in GitHub Desktop.
Save ShadoFlameX/7495098 to your computer and use it in GitHub Desktop.
Custom Map Annotation Callouts on iOS
  1. Create a UIView subclass for the pin callout view.

  2. Create a subclass of MKAnnotationView for your map pins.

  3. Add an instance of the callout view subclass to your MKAnnotationView subclass.

  4. Add a property to toggle the callout view to your MKAnnotationView subclass. This example fades in/out:

    - (void)setShowCustomCallout:(BOOL)showCustomCallout
    {
        [self setShowCustomCallout:showCustomCallout animated:NO];
    }
    
    - (void)setShowCustomCallout:(BOOL)showCustomCallout animated:(BOOL)animated
    {
        if (_showCustomCallout == showCustomCallout) return;
    
        _showCustomCallout = showCustomCallout;
    
        void (^animationBlock)(void) = nil;
        void (^completionBlock)(BOOL finished) = nil;
    
        if (_showCustomCallout) {
            self.calloutView.alpha = 0.0f;
    
            animationBlock = ^{
                self.calloutView.alpha = 1.0f;
                [self addSubview:self.calloutView];
            };
    
        } else {
            animationBlock = ^{ self.calloutView.alpha = 0.0f; };
            completionBlock = ^(BOOL finished) { [self.calloutView removeFromSuperview]; };
        }
    
        if (animated) {
            [UIView animateWithDuration:0.2f animations:animationBlock completion:completionBlock];
    
        } else {
            animationBlock();
            completionBlock(YES);
        }
    }
    
  5. Override hitTest:forEvent: to return the callout view if it is currently being shown and the point is within its frame.

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
        if (self.showCustomCallout && CGRectContainsPoint(self.calloutView.frame, point)) {
            return self.calloutView;
    
        } else {
            return nil;
        }
    }
    
  6. Subclass MKMapView and override hitTest:forEvent: to avoid callouts hiding when tapped:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    {
           UIView *view = [super hitTest:point withEvent:event];
    
           if ([view isKindOfClass:MyCalloutView.class]) {
               return nil; // todo: add a new delegate method to the map protocol to handle callout taps
           } else {
               return view;
           }
    }
    
  7. On your mapview delegate override mapView:didSelectAnnotationView: to show your callout:

    - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
    {
        if ([view isKindOfClass:MyAnnotationView.class]) {
            MyAnnotationView *annotationView = (MyAnnotationView *)view;
            [annotationView setShowCustomCallout:YES animated:YES];
        }
    }
    
  8. On your mapview delegate override mapView:didDeselectAnnotationView: to hide your callout:

    - (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
    {
        if ([view isKindOfClass:[MyAnnotationView class]]) {
            [((MyAnnotationView *)view) setShowCustomCallout:NO animated:YES];
    	}
    }
    
@christophefondacci
Copy link

Overriding the MKMapView:hitTest:forEvent completely prevents the map from calling "didSelectAnnotationView"...

@AartiOza
Copy link

I had implemented your code. but its not working. please help me. My code is in http://stackoverflow.com/questions/32756922/how-to-code-for-mkmapkitview-which-shows-another-container-view-when-user-click link.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment