Conflict between ScrollView pan gesture and panGestureRecognizer
Asked Answered
C

4

9

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.

Consultation answered 28/1, 2018 at 21:48 Comment(9)
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/…Gammadion
@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)Consultation
I don't understand your use case. Which direction are you expecting the user to swipe to dismiss the dialog?Gammadion
@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 itConsultation
Assuming you managed to separate the pan recognisers, how are you going to determine which action the user was trying to perform?Gammadion
@Samah, As I stated in my question, If scroll view offset's y equals to 0, the view controller gets dismissed (when user pans from top to bottom), because in that case, to scroll the view, the user should move his finger towards the topConsultation
Have you ever considered to use only scroll view’s recognizer for both actions?Celebrity
@iWheelBuy, yes I have done exactly that, by using contentOffset.y value. Thanks for suggestion!Consultation
@TigranIskandaryan I'm glad it helped! Please, write you solution as an answer to you question.Celebrity
F
8

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
    }
}
Fineness answered 8/3, 2018 at 0:18 Comment(1)
I've tried that, but without a success. I solved the problem by just using scrollView's contentOffset.y value for dismissing the view.Consultation
M
4

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.

Microbe answered 29/1, 2018 at 9:38 Comment(11)
Thanks for your answer. It works, but only before scrolling (I can both dismiss the VC or scroll). But after some scrolling (and returning to the position where scrollView.contentOffset.y == 0), my pan gesture stops working. Maybe you know how that could be handled?Consultation
Can you share some other code? I don't know if I understand exactly your problemMicrobe
I don't know what exactly you need, so I have added the code for my pan gesture.Consultation
I have also noticed that when I scroll the scroll view to its top, the content offset is not exactly 0.0 . But even if I set it to 0.0 programatically and the break point inside the method gets triggered, my custom pan gesture stops working (only scroll view's pan works)Consultation
What is interactor?Microbe
Interactor is just UIPercentDrivenInteractiveTransition subclass. I have added its code, tooConsultation
Do you return true in gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)?Microbe
Yes, I simply return true from that method (there is no difference in the behaviour if I return false)Consultation
I can take a look at your source code if you want; actually I can't understand very well the layout and the exact behaviour you want to achieveMicrobe
Here what I want to achieve: you know how a view controller appears modally (its animation). Now, I want to reverse its animation via pan gesture (basically, close the view controller via pan gesture making it disappear from the bottom). Sorry, but I can't share the whole source code right now (I just don't have rights to do that).There is nothing personal. However, if you want, I can share the whole code of the animatorConsultation
Returning true when the y offset is less than or equal to zero work had worked for me in a similar implementation: Swift func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return scrollView.contentOffset.y <= 0 } Milquetoast
T
0

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

Trug answered 28/1, 2018 at 21:56 Comment(2)
I have added , but now my custom pan gesture action doesn't get called. I guess the reason is that my scroll view covers the view under it, so it doesn't get the callConsultation
Override scroll view's hitTest:withEvent: ,return the subview if touch point in the frame of subviewSenior
T
0

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)
Twentieth answered 29/1, 2018 at 2:57 Comment(1)
I've done that, but without success, unfortunately. My custom pan "eats" the gesture, so the scroll view's gesture doesn't get called. So, the problem isn't that I can't call my pan gesture, but that I can't call scroll view's pan gesture for scrollingConsultation

© 2022 - 2024 — McMap. All rights reserved.