UIPanGestureRecognizer - 只允许垂直或水平移动

157
我有一个视图,带有一个 UIPanGestureRecognizer 以垂直方向拖动视图。因此,在手势识别回调中,我只更新 y 坐标以移动它。这个视图的 superview 有一个 UIPanGestureRecognizer ,将视图水平拖动,只更新 x 坐标。
问题是第一个 UIPanGestureRecognizer 正在接收事件以使视图垂直移动,所以我不能使用 superview 的手势。
我已经尝试过:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
 shouldRecognizeSimultaneouslyWithGestureRecognizer:
                            (UIGestureRecognizer *)otherGestureRecognizer;

两种方法都可以使用,但我不想这么做。我只希望在移动明显是水平方向时才检测到水平方向。因此,如果UIPanGestureRecognizer有一个方向属性就太好了。

如何实现这种行为?我觉得文档写得很混乱,所以也许有人可以在这里更好地解释一下。


如果你已经找到了解决方案,回答自己的问题并接受答案是可以的。 - jtbandes
@JoeBlow 真的吗?那么,也许你创建了滑动手势类别来接收翻译和手势速度? - Roman Truba
2
我不明白你在说什么。如果你想要检测水平滑动,这完全是内置于操作系统中的。所有的工作都已经为你完成了。你什么也不需要做! :) 只需将此示例中的两行代码粘贴即可.. http://stackoverflow.com/a/20988648/294884 请注意,您可以选择“仅左”、“仅右”或“两者皆可”。 - Fattie
22个回答

0

我很乐意分享我的方法,因为所有其他方法都基于UIGestureRecognizerDelegate或子类化UIPanGestureRecognizer

我的方法基于运行时和交换方法。我不确定这种方法是否完美,但你可以自己测试和改进它。

只需一行代码即可设置任何UIPanGestureRecognizer的方向:

UITableView().panGestureRecognizer.direction = UIPanGestureRecognizer.Direction.vertical

使用 pod 'UIPanGestureRecognizerDirection' 或以下代码:

public extension UIPanGestureRecognizer {

    override open class func initialize() {
        super.initialize()
        guard self === UIPanGestureRecognizer.self else { return }
        func replace(_ method: Selector, with anotherMethod: Selector, for clаss: AnyClass) {
            let original = class_getInstanceMethod(clаss, method)
            let swizzled = class_getInstanceMethod(clаss, anotherMethod)
            switch class_addMethod(clаss, method, method_getImplementation(swizzled), method_getTypeEncoding(swizzled)) {
            case true:
                class_replaceMethod(clаss, anotherMethod, method_getImplementation(original), method_getTypeEncoding(original))
            case false:
                method_exchangeImplementations(original, swizzled)
            }
        }
        let selector1 = #selector(UIPanGestureRecognizer.touchesBegan(_:with:))
        let selector2 = #selector(UIPanGestureRecognizer.swizzling_touchesBegan(_:with:))
        replace(selector1, with: selector2, for: self)
        let selector3 = #selector(UIPanGestureRecognizer.touchesMoved(_:with:))
        let selector4 = #selector(UIPanGestureRecognizer.swizzling_touchesMoved(_:with:))
        replace(selector3, with: selector4, for: self)
    }

    @objc private func swizzling_touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        self.swizzling_touchesBegan(touches, with: event)
        guard direction != nil else { return }
        touchesBegan = true
    }

    @objc private func swizzling_touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        self.swizzling_touchesMoved(touches, with: event)
        guard let direction = direction, touchesBegan == true else { return }
        defer {
            touchesBegan = false
        }
        let forbiddenDirectionsCount = touches
            .flatMap({ ($0.location(in: $0.view) - $0.previousLocation(in: $0.view)).direction })
            .filter({ $0 != direction })
            .count
        if forbiddenDirectionsCount > 0 {
            state = .failed
        }
    }
}

public extension UIPanGestureRecognizer {

    public enum Direction: Int {

        case horizontal = 0
        case vertical
    }

    private struct UIPanGestureRecognizerRuntimeKeys {
        static var directions = "\(#file)+\(#line)"
        static var touchesBegan = "\(#file)+\(#line)"
    }

    public var direction: UIPanGestureRecognizer.Direction? {
        get {
            let object = objc_getAssociatedObject(self, &UIPanGestureRecognizerRuntimeKeys.directions)
            return object as? UIPanGestureRecognizer.Direction
        }
        set {
            let policy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
            objc_setAssociatedObject(self, &UIPanGestureRecognizerRuntimeKeys.directions, newValue, policy)
        }
    }

    fileprivate var touchesBegan: Bool {
        get {
            let object = objc_getAssociatedObject(self, &UIPanGestureRecognizerRuntimeKeys.touchesBegan)
            return (object as? Bool) ?? false
        }
        set {
            let policy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
            objc_setAssociatedObject(self, &UIPanGestureRecognizerRuntimeKeys.touchesBegan, newValue, policy)
        }
    }
}

fileprivate extension CGPoint {

    var direction: UIPanGestureRecognizer.Direction? {
        guard self != .zero else { return nil }
        switch fabs(x) > fabs(y) {
        case true:  return .horizontal
        case false: return .vertical
        }
    }

    static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
        return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
    }
}

-1

PanGestureRecognizer 接口包含以下定义:

unsigned int    _canPanHorizontally:1;
unsigned int    _canPanVertically:1;

我没有检查过,但也许可以通过子类访问。


3
看起来很有前途,但那个API没有暴露出来。使用私有API通常会导致被苹果拒绝。 - William Denniss

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