UIPanGestureRecognizer - 仅垂直或水平

Loc*_*ike 142 uigesturerecognizer ios

我有一个视图,UIPanGestureRecognizer可以垂直拖动视图.所以在识别器回调中,我只更新y坐标来移动它.这个视图的超级视图有一个UIPanGestureRecognizer水平拖动视图,只是更新x坐标.

问题是第一个UIPanGestureRecognizer是事件是垂直移动视图,所以我不能使用超视图手势.

我试过了

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
 shouldRecognizeSimultaneouslyWithGestureRecognizer:
                            (UIGestureRecognizer *)otherGestureRecognizer;
Run Code Online (Sandbox Code Playgroud)

两者都会奏效,但我不希望如此.我希望只有在运动明显水平的情况下才能检测水平.所以如果UIPanGestureRecognizer有一个方向属性会很棒.

我怎样才能实现这种行为?我发现文档很混乱,所以也许有人可以在这里更好地解释它.

Hej*_*azi 207

只需为垂直平移手势识别器执行此操作,它适用于我:

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)panGestureRecognizer {
    CGPoint velocity = [panGestureRecognizer velocityInView:someView];
    return fabs(velocity.y) > fabs(velocity.x);
}
Run Code Online (Sandbox Code Playgroud)

  • @JoeBlow UISwipeGestureRecognizer是一种简单的方法,可以响应滑动手势触发转换,但它是一个离散的手势.如果有人正在寻找一种持续的方法 - 使用手势动画转换 - UIPanGestureRecognizer是要走的路. (19认同)
  • 当使用velocityInView:而不是translateInView时,(0,0)问题不明显. (12认同)
  • 试过这个,但翻译通常是==(0,0),所以它不准确 (3认同)

小智 72

我在@LocoMike提供的答案中创建了一个带子类的解决方案,但是使用了由@Hejazi提供的更有效的初始速度检测机制.我也使用Swift,但如果需要,这应该很容易转换为Obj-C.

优于其他解决方案:

  • 比其他子类解决方案更简单,更简洁.无需管理其他州.
  • 方向检测在发送开始动作之前发生,因此如果滑动错误的方向,则平移手势选择器不会收到任何消息.
  • 确定初始方向后,不再咨询方向逻辑.如果初始方向正确,则会导致激活识别器的一般所需行为,但如果用户的手指未沿着方向完美移动,则在手势开始后不会取消手势.

这是代码:

import UIKit.UIGestureRecognizerSubclass

enum PanDirection {
    case vertical
    case horizontal
}

class PanDirectionGestureRecognizer: UIPanGestureRecognizer {

    let direction: PanDirection

    init(direction: PanDirection, target: AnyObject, action: Selector) {
        self.direction = direction
        super.init(target: target, action: action)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)

        if state == .began {
            let vel = velocity(in: view)
            switch direction {
            case .horizontal where fabs(vel.y) > fabs(vel.x):
                state = .cancelled
            case .vertical where fabs(vel.x) > fabs(vel.y):
                state = .cancelled
            default:
                break
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这绝对是最好的答案.苹果没有在"UIPanGestureRecognizer"中添加这样的功能,这太糟糕了. (4认同)

Loc*_*ike 51

我想出了创建UIPanGestureRecognizer的子类

DirectionPanGestureRecognizer:

#import <Foundation/Foundation.h>
#import <UIKit/UIGestureRecognizerSubclass.h>

typedef enum {
    DirectionPangestureRecognizerVertical,
    DirectionPanGestureRecognizerHorizontal
} DirectionPangestureRecognizerDirection;

@interface DirectionPanGestureRecognizer : UIPanGestureRecognizer {
    BOOL _drag;
    int _moveX;
    int _moveY;
    DirectionPangestureRecognizerDirection _direction;
}

@property (nonatomic, assign) DirectionPangestureRecognizerDirection direction;

@end
Run Code Online (Sandbox Code Playgroud)

DirectionPanGestureRecognizer.m:

#import "DirectionPanGestureRecognizer.h"

int const static kDirectionPanThreshold = 5;

@implementation DirectionPanGestureRecognizer

@synthesize direction = _direction;

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesMoved:touches withEvent:event];
    if (self.state == UIGestureRecognizerStateFailed) return;
    CGPoint nowPoint = [[touches anyObject] locationInView:self.view];
    CGPoint prevPoint = [[touches anyObject] previousLocationInView:self.view];
    _moveX += prevPoint.x - nowPoint.x;
    _moveY += prevPoint.y - nowPoint.y;
    if (!_drag) {
        if (abs(_moveX) > kDirectionPanThreshold) {
            if (_direction == DirectionPangestureRecognizerVertical) {
                self.state = UIGestureRecognizerStateFailed;
            }else {
                _drag = YES;
            }
        }else if (abs(_moveY) > kDirectionPanThreshold) {
            if (_direction == DirectionPanGestureRecognizerHorizontal) {
                self.state = UIGestureRecognizerStateFailed;
            }else {
                _drag = YES;
            }
        }
    }
}

- (void)reset {
    [super reset];
    _drag = NO;
    _moveX = 0;
    _moveY = 0;
}

@end
Run Code Online (Sandbox Code Playgroud)

这将仅在用户开始拖动所选行为时触发手势.将direction属性设置为正确的值,然后全部设置.

  • "这不能正常工作" - 最糟糕的错误报告 (50认同)
  • 在当前的实现中,`DirectionPanGestureRecognizer`将忽略快速拖动,除非你设置`kDirectionPanThreshold = 20`左右,在这种情况下它可以给出错误警报.我建议把`abs(_moveX)> abs(_moveY)`代替`abs(_moveX)> kDirectionPanThreshold`并分别改变水平情况. (5认同)
  • 我应该补充一点,这对我也有帮助,但是我必须添加以使得触发器的手势识别器在if的else部分中,在`_drag = YES`行下我添加了`self.state = UIGestureRecognizerStateChanged;` (2认同)

小智 11

我试图用UIPanGestureRecognizer水平约束有效区域.

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {

        UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer;
        CGPoint velocity = [panGesture velocityInView:panGesture.view];

        double radian = atan(velocity.y/velocity.x);
        double degree = radian * 180 / M_PI;

        double thresholdAngle = 20.0;
        if (fabs(degree) > enableThreshold) {
            return NO;
        }
    }
    return YES;
}
Run Code Online (Sandbox Code Playgroud)

然后,只能在水平阈值范围内滑动才能触发此平移手势.

  • 很好的答案.当我混合使用UIScrollView手势和常规手势时,这确实帮助了我.我认为这个例子意味着说"thresholdAngle"而不是"enableThreshold".你应该很少使用atan(),因为它可以产生NAN.请改用atan2(). (2认同)

Sia*_*Alp 8

Swift 3.0回答:只是处理垂直手势

    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    if let pan = gestureRecognizer as? UIPanGestureRecognizer {
        let velocity = pan.velocity(in: self)
        return fabs(velocity.y) > fabs(velocity.x)
    }
    return true

}
Run Code Online (Sandbox Code Playgroud)


Bor*_*zin 6

以下解决方案解决了我的问题:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if ([gestureRecognizer.view isEqual:self.view] && [otherGestureRecognizer.view isEqual:self.tableView]) {
        return NO;
    }
    return YES;
}
Run Code Online (Sandbox Code Playgroud)

这实际上只是检查pan是否在主视图或tableView上.

  • 为什么调用-isEqual:比较两个视图是否相同?简单的身份检查就足够了.gestureRecognizer.view == self.view (3认同)

Ces*_*ela 6

斯威夫特3版的李的懒惰回答

import UIKit
import UIKit.UIGestureRecognizerSubclass

enum PanDirection {
    case vertical
    case horizontal
}

class UIPanDirectionGestureRecognizer: UIPanGestureRecognizer {

    let direction : PanDirection

    init(direction: PanDirection, target: AnyObject, action: Selector) {
        self.direction = direction
        super.init(target: target, action: action)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)

        if state == .began {

            let vel = velocity(in: self.view!)
            switch direction {
            case .horizontal where fabs(vel.y) > fabs(vel.x):
                state = .cancelled
            case .vertical where fabs(vel.x) > fabs(vel.y):
                state = .cancelled
            default:
                break
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


den*_*T30 5

这是Swift 5中的自定义平移手势

U可以约束它的方向以及该方向上的最大角度,也可以约束它在该方向上的最小速度。

enum PanDirection {
    case vertical
    case horizontal
}

struct Constaint {
    let maxAngle: Double
    let minSpeed: CGFloat

    static let `default` = Constaint(maxAngle: 50, minSpeed: 50)
}


class PanDirectionGestureRecognizer: UIPanGestureRecognizer {

    let direction: PanDirection

    let constraint: Constaint


    init(direction orientation: PanDirection, target: AnyObject, action: Selector, constraint limits: Constaint = Constaint.default) {
        direction = orientation
        constraint = limits
        super.init(target: target, action: action)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)
        let tangent = tan(constraint.maxAngle * Double.pi / 180)
        if state == .began {
            let vel = velocity(in: view)
            switch direction {
            case .horizontal where abs(vel.y)/abs(vel.x) > CGFloat(tangent) || abs(vel.x) < constraint.minSpeed:
                state = .cancelled
            case .vertical where abs(vel.x)/abs(vel.y) > CGFloat(tangent) || abs(vel.y) < constraint.minSpeed:
                state = .cancelled
            default:
                break
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样调用:

    let pan = PanDirectionGestureRecognizer(direction: .vertical, target: self, action: #selector(self.push(_:)))
    view.addGestureRecognizer(pan)

    @objc func push(_ gesture: UIPanGestureRecognizer){
        if gesture.state == .began{
            // command for once
        }
    }
Run Code Online (Sandbox Code Playgroud)

或者

    let pan = PanDirectionGestureRecognizer(direction: .horizontal, target: self, action: #selector(self.push(_:)), constraint: Constaint(maxAngle: 5, minSpeed: 80))
    view.addGestureRecognizer(pan)
Run Code Online (Sandbox Code Playgroud)