8
votes

I want to drop a pin on my MKMapView when the user single taps on the map. I have the pin code working, I have the single tap working, but when I double tap to zoom I get a single tap first. Here's my code to get the recognizers setup:

    self.doubleTap = [[UITapGestureRecognizer alloc]
                      initWithTarget:self action:@selector(handleDoubleTap:)];
    self.doubleTap.numberOfTapsRequired = 2;
    self.doubleTap.numberOfTouchesRequired = 1;
    [mapView_ addGestureRecognizer:doubleTap_];

    self.singleTap = [[UITapGestureRecognizer alloc]
                      initWithTarget:self action:@selector(handleGesture:)];
    self.singleTap.numberOfTapsRequired = 1;
    self.singleTap.numberOfTouchesRequired = 1;
    [self.singleTap requireGestureRecognizerToFail: doubleTap_];
    [mapView_ addGestureRecognizer:singleTap_];

Now, this is not surprising, to quote Apple:

Note: In the case of the single-tap versus double-tap gestures, if a single-tap gesture recognizer doesn’t require the double-tap recognizer to fail, you should expect to receive your single-tap actions before your double-tap actions, even in the case of a double tap. This is expected and desirable behavior because the best user experience generally involves stackable actions.

So I added requireGestureRecognizerToFail to my single tap recognizer.

[singleTap requireGestureRecognizerToFail: doubleTap];

and this ensures that my single tap recognizer doesn't get double taps.

But...

Now my double tap recognizer gets the double taps and MKMapView doesn't get them. I've tried setting cancelsTouchesInView to NO in the recognizer, but that didn't help either.

So I need a way to either prevent my single tap recognizer from getting double taps (which seems unlikely) or to get my double tap event to my mapView.

4
Can you show the code you're using to add the gesture recognizers? Which iOS version are you testing with? Have you tried returning YES from the shouldRecognizeSimultaneouslyWithGestureRecognizer delegate method?user467105
question edited to have more code. I'm testing with iOS5.1. Haven't tried the shouldRecognizeSimultaneouslyWithGestureRecognizer delegate, I'll do that now.Paul Cezanne
shouldRecognizeSimultaneouslyWithGestureRecognizer=YES did the trick, Make that an answer and I'll mark it as the correct one. Thanks!Paul Cezanne

4 Answers

2
votes

The code posted seems to work fine as-is in iOS 6.
The double-tap is handled automatically by the map view without interfering with the single-tap.

In iOS 5.x, to get the map view to execute its normal double-tap handling without interfering with your single-tap, implement the shouldRecognizeSimultaneouslyWithGestureRecognizer delegate method and return YES from it (need to set the delegate property of the double-tap gesture recognizer only).

Not sure why this is not necessary in iOS 6.

4
votes

Working Perfectly on iOS 8, double tap action is null

  - (void)viewDidLoad 
    {
        [super viewDidLoad];

        UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:nil];
        doubleTap.numberOfTapsRequired = 2;
        doubleTap.numberOfTouchesRequired = 1;
        [self.mapView addGestureRecognizer:doubleTap];

        UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
        singleTap.numberOfTapsRequired = 1;
        singleTap.numberOfTouchesRequired = 1;
        [singleTap requireGestureRecognizerToFail: doubleTap];
        [self.mapView addGestureRecognizer:singleTap];
     }

   - (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
     {
            if (gestureRecognizer.state != UIGestureRecognizerStateEnded)
                return;
            //Do your work ...
     }
0
votes

I found this post that helped me quite a lot...
Basically, you need to find Apple's gesture recognizer, and make sure you don't compete with it.

Jonathan's solution was almost perfect, except one thing:
Since in iOS 7 MKView has all it's gestures in a subview. It's kinda convenient if you want to distinguish which are yours (added to the MKMap directly) and which are Apple's, but it might break somewhere in the future....

What I ended up doing was to go over all the map's subview, hoping it'll be flexible enough to withstand at least some future changes...

Looks something like this:

- (void) hackInTheGestureRecognizer {

    for (UIView *aSubview in [[self theMap] subviews]) {

        if ([aSubview isMemberOfClass:[UIView class]]) {

            for (UIGestureRecognizer *aRecognizer in [aSubview gestureRecognizers]) {

                if ([aRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {

                    if ([(UITapGestureRecognizer *) aRecognizer numberOfTapsRequired] == 2) {

                        [[self singleTapRecognizer] requireGestureRecognizerToFail:aRecognizer];
                    }
                }
            }
        }
    }
}
0
votes

Swift 3 version on iOS 10:

// Gesture recognizer set up in Interface Builder
@IBOutlet weak var singleTapRecognizer: UITapGestureRecognizer!

override func viewDidLoad() {
    // double tap recognizer has no action
    let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: nil)
    doubleTapRecognizer.numberOfTapsRequired = 2
    doubleTapRecognizer.numberOfTouchesRequired = 1
    mapView.addGestureRecognizer(doubleTapRecognizer)

    singleTapRecognizer.require(toFail: doubleTapRecognizer)
}

Note that the single tap is created in IB. It could easily be created in code like so:

    let singleTapRecognizer = UITapGestureRecognizer(target: self, action: Selector("someAction"))
    singleTapRecognizer.numberOfTapsRequired = 1
    singleTapRecognizer.numberOfTouchesRequired = 1
    mapView.addGestureRecognizer(singleTapRecognizer)