拦截UIScrollView上的平移手势会导致滚动失效。

45

我有一个垂直滚动的UIScrollView。我想在保留默认垂直滚动行为的同时,也处理水平拖动。我在滚动视图上放置了一个透明的UIView,并添加了一个拖动手势识别器。这样我就可以很好地获取拖动,但是滚动视图却无法接收到任何手势。

我实现了以下UIPanGestureRecognizerDelegate方法,希望将我的手势识别器限制为仅水平拖动,但这并没有起作用:

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
    // Only accept horizontal pans here.
    // Leave the vertical pans for scrolling the content.
    CGPoint translation = [gestureRecognizer translationInView:self.view];
    BOOL isHorizontalPan = (fabsf(translation.x) > fabsf(translation.y));
    return  isHorizontalPan;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return (otherGestureRecognizer == _scrollView.panGestureRecognizer);
}

1
你尝试过 [panGesture setCancelsTouchesInView:NO]; 吗?在 shouldRecognizeSimultaneouslyWithGestureRecognizer 中简单地返回 YES 也可能有助于你找出问题所在。 - Mick MacCallum
刚试了一下,没有任何区别。 - TotoroTotoro
4个回答

53

好的,我解决了。我需要做两件事情才能使它正常工作:

1)将自己的 pan recognizer 附加到滚动视图本身,而不是在其上方附加到另一个视图。

2)这个 UIGestureRecognizerDelegate 方法可以防止出现默认滚动视图和我的滚动视图同时被调用时发生的怪异行为。

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

2
shouldRecognizeSimultaneouslyWithGestureRecognizer的默认行为是返回NO,因此添加是不必要的。 - Gagan Singh
4
将返回值更改为“是”即可正常工作。 - P.L.

18

我也遇到了同样的问题,解决方法如下:

1) 在滚动视图上添加自己的拖动手势识别器。

2) 在gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:上返回YES。

这将允许两个手势同时工作。这意味着,在垂直滚动时,您的panGesture delegate和scrollView Delegate将同时被触发。如果是水平滚动,则只调用panGesture delegate。

3) 在我的panGesture delegate中,检测是否为水平滚动,如果不是,则忽略。


1
这正是我在自己的答案中推荐的。 - TotoroTotoro
12
@BlackRider不是这样的。这会返回YES。 - Radu Simionescu
谢谢 gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer 解决了问题。 - atulkhatri

10

快速回答:

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
    }
}

1

对于极其谨慎的程序员:

你不能随随便便地返回 true。你应该检查“其他”手势是否确实是所讨论的滚动视图。

这很复杂,因为滚动视图有许多手势识别器附加在上面。

你可以这样做来查看它们所有,并且在你阅读本文时 UIKit 中有多少个。

print("count \(primaryScroll.gestureRecognizers?.count)")
print("count \(primaryScroll.gestureRecognizers)") 

所以,完整的解决方案是:

import UIKit
class Examp: UIViewController, UIGestureRecognizerDelegate {
    
    var primaryScroll: UIScrollView ... your scroll view in question

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let p = UIPanGestureRecognizer(target: self, action: #selector(panned))
        p.delegate = self
        primaryScroll.addGestureRecognizer(p)
    }
    
    func gestureRecognizer(
     _ g: UIGestureRecognizer,
     shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool {

        guard
         let gg = primaryScroll.gestureRecognizers,
         gg.contains(other) else { return false }

        return true
    }

    @objc func panned(p: UIPanGestureRecognizer) {
        let v = p.velocity(in: view).x
        if p.state == .ended && abs(v) > 300 { print("swiped") }
        // etc ...
    }

就是这样。

如果你不检查other手势是否确实是涉及到的滚动视图,你将会面临一个非常难以调试的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接