9
votes

I have a UIScrollView in a UIViewController, which is showed modally by a segue, and an additional UIPanGestureRecognizer do dismiss the view controller by pan. This gesture only works if

 scrollView.contentOffset.y == 0

The problem is, now two pan gestures conflict with each other, and I can't scroll the view any more.

To solve this I have tried to use gestureRecognizer(_: shouldRecognizeSimultaneouslyWith:) method, returning yes, and also, I've tried to add my custom pan gesture to UIScrollView pan gesture recognizer like this:

 scrollView.panGestureRecognizer.addTarget(self, action: #selector(handlePanGesture(_:)))

But these don't solve the problem If you know how to solve this issue, I would appreciate your help.

EDITED

Here is the code for my pan gesture that dismisses the view controller:

     @IBAction func handlePanGesture(_ sender: UIPanGestureRecognizer) {
    let percentThreshold: CGFloat = 0.3

    if scrollView.contentOffset.y == 0 {
        let translation = sender.translation(in: view)
        let verticalMovement = translation.y / view.bounds.height
        let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
        let downwardMovementPercent = fminf(downwardMovement, 1.0)
        let progress = CGFloat(downwardMovementPercent)

        guard let interactor = interactor else {return}
        switch sender.state {
        case .began:
            interactor.hasStarted = true
            dismiss(animated: true, completion: nil)
        case .changed:
            interactor.shouldFinish = progress > percentThreshold
            interactor.update(progress)
        case .cancelled:
            interactor.hasStarted = false
            interactor.cancel()
        case .ended:
            interactor.hasStarted = false
            interactor.shouldFinish ? interactor.finish() : interactor.cancel()
        default:
            break
        }

    }
}

EDITED_2 Here is the code for Interactor:

class Interactor: UIPercentDrivenInteractiveTransition {
var hasStarted = false
var shouldFinish = false 

}

P.s. I know that there is a bunch of similar questions but they don't work for me.

4
If you're using a UINavigationController, it should automatically swipe to go back, unless you explicitly remove the gesture recogniser. developer.apple.com/documentation/uikit/uinavigationcontroller/…Samah
@Samah, the problem isn't related to going back. My view controller is shown modally, so it can't go back by swipe (even if it is in a navigation controller)Tigran Iskandaryan
I don't understand your use case. Which direction are you expecting the user to swipe to dismiss the dialog?Samah
@Samah, my view controller appears modally, so it appears from the bottom of the screen. I dismiss it by panning from the top to bottom. The problem is, my scroll view is also scrollable vertically so there is a conflict between two pan gestures, and I need to resolve itTigran Iskandaryan
Have you ever considered to use only scroll view’s recognizer for both actions?iWheelBuy

4 Answers

7
votes

To allow scrolling when a UIPanGestureRecognizer is on a ScrollView you need to create a UIGestureRecognizerDelegate that returns true on gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)

If you don't do this, scrolling will not be possible on the ScrollView.

This is done like so:

let scrollViewPanGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:)))
scrollViewPanGesture.delegate = self
scrollView.addGestureRecognizer(scrollViewPanGesture)

extension ViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}
4
votes

I'm not sure but you can try adding the ViewController as a UIPanGestureRecognizer delegate of the swipe to dismiss pan gesture and implementing gestureRecognizerShouldBegin(_:);

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return scrollView.contentOffset.y == 0
}

So the gesture to dismiss will start only if the content offset is zero.

0
votes

Add a subview under the scrollview and add the pan gesture to it instead of adding it to self.view that for sure will conflict with the scrollview's one

0
votes

You did the right way when implemented gestureRecognizer(_: shouldRecognizeSimultaneouslyWith:)
But you must set the gesture delegate to current View Controller first:

let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(handlePanGesture(_:)))
panGesture.delegate = self // <--THIS
scrollView.addGestureRecognizer(panGesture)