UIGestureRecognizer.State is 'possible' and not 'recognized' every other click on an MKMapView

Viewed 865

I am trying to understand a reproducible bug with my gesture recognisers. I have 2 recognisers on an MKMapView, one UITapGestureRecognizer and one UILongPressGestureRecogniser. Both of them work as expected the first time, however, if I use the long press (which adds an annotation to the map) the next tap gesture will return in the 'possible' state but never hit the 'recognized' state.

▿ Optional<Array<UIGestureRecognizer>>
  ▿ some : 2 elements
    - 0 : <UITapGestureRecognizer: 0x7fda7543ebc0; state = Possible; view = <MKMapView 0x7fda78026e00>>
    - 1 : <UILongPressGestureRecognizer: 0x7fda7543e8c0; state = Possible; view = <MKMapView 0x7fda78026e00>; numberOfTapsRequired = 0; minimumPressDuration = 0.2>

After I tap once, and nothing happens, a second tap will then perform the associated function i.e. make it to the recognized state.

I am intercepting all the clicks on the window and the tap definitely takes place each time but the first one after a long press never seems to become accepted. Is there something I'm missing here? The gestures are added as below:

let mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
        mapView.addGestureRecognizer(mapTap)

let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(mapLongPress(_:)))
            pressGesture.minimumPressDuration = 0.2
            pressGesture.numberOfTouchesRequired = 1
            mapView.addGestureRecognizer(pressGesture)

Could this be to do with the other gestures which are added by default on an MKMapView?

2 Answers

In your case you expect that the tap recognizer as well as the long press recognizer operate simultaneously: When you tap the view, both should start the recognition process. When you end the tap before the minimum tap time for the long press, the tap gesture should fire, but when you end the tap later, the long press gesture should fire.
But the Apple docs say:

UIKit normally allows the recognition of only one gesture at a time on a single view. Recognizing only one gesture at a time is usually preferable because it prevents user input from triggering more than one action at a time. However, this default behavior can introduce unintended side effects. For example, in a view that contains both pan and swipe gesture recognizers, swipes are never recognized. Because the pan gesture recognizer is continuous, it always recognizes its gesture before the swipe gesture recognizer, which is discrete.

In your case, the long tap gesture recognizer is continuous while the tap gesture recognizer is discrete, so there could be a problem in recognizing the tap.

I would thus try to explicitly allow both recognizers to simultaneous recognice their gestures. An example how to do this is given here.
As soon as the long press recognizer fires, you could cancel the recognition operation of the tap recognizer.
Hope this helps!

I tried using your code and got the same result.

I solved it with a tricky solution. I hope it would be helpful for you

 mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
 mapTap.delegate = self
 mapView.addGestureRecognizer(mapTap)
 
 pressGesture = UILongPressGestureRecognizer(target: self, action: 
                    #selector(mapLongPress(_:)))
                    pressGesture.minimumPressDuration = 0.2
                    pressGesture.numberOfTouchesRequired = 1
 mapView.addGestureRecognizer(pressGesture)

 @objc func mapTapped(_ gesture: UITapGestureRecognizer) {
    // your code
 }
 

 @objc func mapLongPress(_ gesture: UILongPressGestureRecognizer) {
    // your code
        
    if gesture.state == .began {
        mapTap.isEnabled = false
    } else if gesture.state == .cancelled || gesture.state == .ended {
        mapTap.isEnabled = true
    }
 }
   
 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
 }

Related