9

I have a mapview that uses MKCircles to display radius information for certain user actions.

What I want to do, is allow the user to dismiss the MKCircle when they touch the map. However, I would like the MKCircle to NOT dismiss should the user touch any of the other pins or the MKCircle itself.

Any ideas?

Here is my current code, which dismisses the MKCircle when any part of the map is touched:

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(deactivateAllRadars)];
[tap setCancelsTouchesInView:NO];
[_mapView addGestureRecognizer:tap];
JimmyJammed
  • 9,598
  • 19
  • 79
  • 146

2 Answers2

7

In the deactivateAllRadars method, you can use hitTest:withEvent: to tell whether an MKAnnotationView has been tapped or not.

An example of this is shown in How can I catch tap on MapView and then pass it to default gesture recognizers? (it's the second code sample).

This will let you avoid removing the circle if an annotation has been tapped.

If an annotation has not been tapped, you can then check if an MKCircle was tapped by getting the coordinates of the touch (see How to capture Tap gesture on MKMapView for an example) and seeing if the distance from the touch to the circle's center is greater than its radius.

Note that the deactivateAllRadars should be changed to deactivateAllRadars:(UITapGestureRecognizer *)tgr because it will need information from the associated gesture recognizer. Also be sure to add a colon at the end of the method's selector where you alloc+init tap.

For example:

-(void)deactivateAllRadars:(UITapGestureRecognizer *)tgr
{
    CGPoint p = [tgr locationInView:mapView];

    UIView *v = [mapView hitTest:p withEvent:nil];

    id<MKAnnotation> ann = nil;

    if ([v isKindOfClass:[MKAnnotationView class]])
    {
        //annotation view was tapped, select it...
        ann = ((MKAnnotationView *)v).annotation;
        [mapView selectAnnotation:ann animated:YES];
    }
    else
    {
        //annotation view was not tapped, deselect if some ann is selected...
        if (mapView.selectedAnnotations.count != 0)
        {
            ann = [mapView.selectedAnnotations objectAtIndex:0];
            [mapView deselectAnnotation:ann animated:YES];
        }


        //remove circle overlay if it was not tapped...        
        if (mapView.overlays.count > 0)
        {
            CGPoint touchPoint = [tgr locationInView:mapView];

            CLLocationCoordinate2D touchMapCoordinate 
              = [mapView convertPoint:touchPoint toCoordinateFromView:mapView];

            CLLocation *touchLocation = [[CLLocation alloc] 
              initWithLatitude:touchMapCoordinate.latitude 
              longitude:touchMapCoordinate.longitude];

            CLLocation *circleLocation = [[CLLocation alloc] 
              initWithLatitude:circleCenterLatitude 
              longitude:circleCenterLongitude];

            CLLocationDistance distFromCircleCenter 
              = [touchLocation distanceFromLocation:circleLocation];

            if (distFromCircleCenter > circleRadius)
            {
                //tap was outside the circle, call removeOverlay...
            }
        }
    }
}
Community
  • 1
  • 1
  • It doesn't work on iOS 7, an MKNewAnnotationContainerView is returned in the hit test. Any idea on how to fix it? – Rodrigo Ruiz Jul 27 '14 at 22:04
  • @RodrigoRuiz, MKNewAnnotationContainerView (private class) is what's returned when you tap on the map area which this particular code _doesn't care about_. When you tap on an annotation, iOS 7 still returns MKAnnotationView (documented class) which is what the code _is checking for_. If still an issue, please start a new Question with details. –  Jul 28 '14 at 11:16
  • Sorry, maybe I wasn't much clear, what I meant was that when you tap an annotation, it doesn't return an MKAnnotationView, it returns the MKNewAnnotationContainerView, same as when you tap the map area. – Rodrigo Ruiz Jul 28 '14 at 17:47
  • @RodrigoRuiz, I am not able to duplicate that unfortunately. Please post a new Question with details including the viewForAnnotation and gesture recognizer methods. Thanks. –  Jul 28 '14 at 17:54
  • Sorry, maybe I wasn't much clear, what I meant was that when you tap an annotation, it doesn't return an MKAnnotationView, it returns the MKNewAnnotationContainerView, same as when you tap the map area. I just tested it and what you said is true if you use the default MKPinAnnotationView, but if you return a custom annotation view in the mapView:viewForAnnotation: delegate method, it aways returns the MKNewAnnotationContainerView. – Rodrigo Ruiz Jul 28 '14 at 18:04
  • I'm sorry to waste your time, just figured it out that it was my fault, I was overriding the hitTest method of my custom pin =) – Rodrigo Ruiz Jul 28 '14 at 18:13
  • There is one more problem though, when I tap 5 points away from the pin, the MKNewAnnotationContainerView is returned, but the pin is selected. – Rodrigo Ruiz Jul 28 '14 at 18:20
3

This is my Swift 2.1 compatible version:

func didTapOnMap(recognizer: UITapGestureRecognizer) {
    let tapLocation = recognizer.locationInView(self)
    if let subview = self.hitTest(tapLocation, withEvent: nil) {
        if subview.isKindOfClass(NSClassFromString("MKNewAnnotationContainerView")!) {
            print("Tapped out")
        }
    }
}

MKNewAnnotationContainerView is a private inner class, so you cannot compare directly like:

if subview is MKNewAnnotationContainerView {
}
Luca Davanzo
  • 21,000
  • 15
  • 120
  • 146
  • Im way late to this, but when you say "private inner class", does this literally just mean that this is a private class that is inside of another class (perhaps MKAnnotationView)? – Dallas Sep 20 '17 at 06:14
  • You're right, "inner" is misleading: I don't know where exactly MKNewAnnotationContainerView is defined, but it's private for sure. If you find any documentation, please feel free to update answer! – Luca Davanzo Sep 20 '17 at 08:22
  • 1
    NSClassFromString("MKNewAnnotationContainerView") returns nil in Swift 4.2. Using NSClassFromString("MKAnnotationContainerView") fixed the issue. – MrRhoads Oct 15 '18 at 06:05