Two UIPanGestureRecognizers in the same UIView Invoked Simultaneously

50 Views Asked by At

I want two UIPanGestureRecognizers to work simultaneously in a UIView. When Swiped above the center, panGesture1 would get invoked, and swiping below the center invokes panGesture2. Using both of these simultaneously has to be possible.

let panGesture1 = UIPanGestureRecognizer()
let panGesture2 = UIPanGestureRecognizer()

self.panGesture1.addTarget(self, action: #selector(self.panGesture1Detected(_:)))
self.panGesture1.delegate = self
self.panGesture1.minimumNumberOfTouches = 1
self.panGesture1.maximumNumberOfTouches = 1
self.panGesture1.cancelsTouchesInView = false
self.panGesture1.delaysTouchesBegan = false
self.panGesture1.delaysTouchesEnded = true
self.panGesture1.requiresExclusiveTouchType = false
self.view.addGestureRecognizer(self.panGesture1)

self.panGesture2.addTarget(self, action: #selector(self.panGesture2Detected(_:)))
self.panGesture2.delegate = self
self.panGesture2.minimumNumberOfTouches = 1
self.panGesture2.maximumNumberOfTouches = 1
self.panGesture2.cancelsTouchesInView = false
self.panGesture2.delaysTouchesBegan = false
self.panGesture2.delaysTouchesEnded = true
self.panGesture2.requiresExclusiveTouchType = false
self.view.addGestureRecognizer(self.panGesture2)



func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
   return true
}


func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer == self.panGesture1 {
            if gestureRecognizer.location(in: self.view).y < self.view.bounds.midY {
                //Pangesture 1 is above the first half of view. So good.
                return true
            } else {
                return false
            }
        }

        else if gestureRecognizer == self.panGesture2 {
            if gestureRecognizer.location(in: self.view).y > self.view.bounds.midY {
                //Pangesture 2 is below the first half of view. So good.
                return true
            } else {
                return false
            }
        }
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {

        return true
}

Current Issue

When the first touch pans, it recognizes the appropriate pan gesture. When the second touch comes down, it assumes this is for the currently active pan gesture. So basically, simultaneous activation of both pan gestures is not possible.

Easy Way Out

Put a dummy UIView on one half of the screen and have one of the pan gestures get activated on there?

Is this even possible?

Is this even possible with two pan gestures in the same view?

1

There are 1 best solutions below

4
Matic Oblak On

I do hope you get an answer that uses 2 gesture recognisers. I toyed with it myself but gave up.

There may still be something possible with enabling isMultipleTouchEnabled on your view but I didn't manage to make it work.

Anyway, the pan gestures are the easiest to implement by yourself. They have absolutely no fine-tuning. You just need to track dragging. Here is a very simple implementation of such a thing using touch events:

class CustomDragRecogniser {

    private var currentTouch: UITouch?

    // Control:
    var mayBegin: (_ touch: UITouch) -> Bool = { _ in true }
    var onBegin: (_ touch: UITouch) -> Void = { _ in }
    var onDrag: (_ touch: UITouch) -> Void = { _ in }
    var onEnded: (_ touch: UITouch) -> Void = { _ in }

    // Input:
    func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard currentTouch == nil else { return } // Already in progress
        if let matching = touches.first(where: { mayBegin($0) }) {
            currentTouch = matching
            onBegin(matching)
        }
    }

    func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let matching = touches.first(where: { $0 === currentTouch }) {
            onDrag(matching)
        }
    }

    func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let matching = touches.first(where: { $0 === currentTouch }) {
            currentTouch = nil
            onEnded(matching)
        }
    }

    func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let matching = touches.first(where: { $0 === currentTouch }) {
            currentTouch = nil
            onEnded(matching)
        }
    }

}

class SimultaneousGesturesViewController: UIViewController {

    private var recognisers: [CustomDragRecogniser] = []


    override func viewDidLoad() {
        super.viewDidLoad()

        view.isMultipleTouchEnabled = true

        let topRecogniser = CustomDragRecogniser()
        topRecogniser.mayBegin = { [weak self] touch in
            guard let view = self?.view else { return false }
            return touch.location(in: view).y < view.bounds.height*0.5
        }
        topRecogniser.onDrag = { [weak self] touch in
            guard let view = self?.view else { return }
            print("G1 moved to \(touch.location(in: view))")
        }

        let bottomRecogniser = CustomDragRecogniser()
        bottomRecogniser.mayBegin = { [weak self] touch in
            guard let view = self?.view else { return false }
            return touch.location(in: view).y >= view.bounds.height*0.5
        }
        bottomRecogniser.onDrag = { [weak self] touch in
            guard let view = self?.view else { return }
            print("G2 moved to \(touch.location(in: view))")
        }

        recognisers = [topRecogniser, bottomRecogniser]
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        recognisers.forEach { $0.touchesBegan(touches, with: event) }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        recognisers.forEach { $0.touchesMoved(touches, with: event) }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        recognisers.forEach { $0.touchesEnded(touches, with: event) }
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        recognisers.forEach { $0.touchesCancelled(touches, with: event) }
    }

}

You should be able to pack this into a view or another tool to mask away most or of the code for reusability.