Melodius
Melodius

Reputation: 2785

Custom UIGestureRecognizer for a two finger gesture

I have a custom UIGestureRecognizer for a two finger gesture that works perfectly except for it being very picky about how simultaneously the fingers have to touch the iOS-device for touchesBegan to be called with 2 touches. touchesBegan is often called with only one Touch even though I am trying to use two fingers.

Is there any way to make recognition for the number of Touches more forgiving in regards to how simultaneously you have to place your fingers on the touch screen?

I've noticed that a two finger tap is recognized even when you place first one finger and then another much later while still holding the first finger down.

Here is the code for my touchesBegan function:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {

      if touches.count != 2 {
         state = .failed
         return
      }

      // Capture the first touch and store some information about it.
      if trackedTouch == nil {
         trackedTouch = touches.min { $0.location(in: self.view?.window).x < $1.location(in: self.view?.window).x }
         strokePhase = .topSwipeStarted
         topSwipeStartPoint = (trackedTouch?.location(in: view?.window))!
         // Ignore the other touch that had a larger x-value
         for touch in touches {
            if touch != trackedTouch {
               ignore(touch, for: event)
            }
         }
      }
   }

Upvotes: 8

Views: 2483

Answers (5)

Christian Schnorr
Christian Schnorr

Reputation: 10776

For two-finger gestures, touchesBegan is most likely going to be called twice: once you put the first finger on the screen, and once for the second one.

In the state you keep, you should keep track of both touches (or for that matter, all current touches), and only start the gesture once both touches have been received and the gesture's start condition has been met.

public class TwoFingerGestureRecognizer: UIGestureRecognizer {
    private var trackedTouches: Set<UITouch> = []

    public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        for touch in touches {
            if self.trackedTouches.count < 2 {
                self.trackedTouches.insert(touch)
            }
            else {
                self.ignore(touch, for: event)
            }
        }

        if self.trackedTouches.count == 2 {
            // put your current logic here
        }
    }

    public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
    }

    public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        self.trackedTouches.subtract(touches)
    }

    public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
        self.trackedTouches.subtract(touches)
    }

    public override func reset() {
        super.reset()

        self.trackedTouches = []
    }
}

Upvotes: 2

Popmedic
Popmedic

Reputation: 1871

Not sure why the other guys answering with using touchesMoved:withEvent did not answer your question, but maybe you need a github example.

Double touch move example.

Upvotes: 0

Ionescu Vlad
Ionescu Vlad

Reputation: 151

Is touchesMoved an option in order for you to achieve the desire outcome? Also you could implement a counter before setting the state to failed Don't forget to set isMultipleTouchEnabled = true

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    if touches.count != 2 {
        print("we do NOT have two touches")
        if counter > 100   { // 100 "more fogiven"
            state = .failed
        }
        counter += 1
        return
    } else {
        print("we have to two touches")
    }

Upvotes: -1

Taylor2412
Taylor2412

Reputation: 36

I'd recommend making your code a little more forgiving. Although touchesBegan/Moved/Ended/Cancelled respond to events of "one or more touches" (as stated in the Apple docs) relying on the precision of a user to simultaneously touch the screen with 2 fingers is not ideal. This is assuming you have multi-touch enabled, which it sounds like you do.

Try tracking the touches yourself and executing your logic when you're collection of touches reaches 2. Obviously you'll also have to track when your touch amount becomes more or less and handle things accordingly, but I'd guess you're already handling this if you're gesture is meant to be for 2 fingers only (aside from the extra logic you'd have to add in touchesBegan).

Upvotes: 1

ɯɐɹʞ
ɯɐɹʞ

Reputation: 1068

don't worry about touchesBegan:withEvent: instead use touchesEnded:withEvent: or touchesMoved:withEvent: if the end state does not contain both fingers, set it as .failed otherwise set it as .ended

tapping the screen with more than one finger simultaneously is impossible, so during touchesMoved:withEvent: you will find two fingers. I'm not sure about touchesEnded:withEvent: this one probably won't work since removing two fingers simultaneously is just as hard as applying two fingers simultaneously, but it's worth a try to see how it reacts.

Upvotes: 1

Related Questions