在iOS中检测PAN手势的方向

40
在我的应用程序中有一个图像视图。我正在向该图像视图添加PAN手势。这个功能运作正常。图像视图处于横向模式。 当用户向右滑动时,我想要在标签中增加计数,并且当用户向左滑动时,减少计数。我搜索了很多但是没有找到解决方法。请问有谁能帮助我如何检测用户滑动的方向(向左/向右)?

我正在为那个图像视图添加PAN手势。你是指手势识别器吗? - user529758
请查看以下链接:https://dev59.com/AWbWa4cB1Zd3GeqPY6ns#27130070 - onmyway133
7个回答

94
在您的手势识别器的目标选择器中使用 - (CGPoint)velocityInView:(UIView *)view;
- (void)panRecognized:(UIPanGestureRecognizer *)rec
{
    CGPoint vel = [rec velocityInView:self.view];
    if (vel.x > 0)
    {
        // user dragged towards the right
        counter++;
    }
    else
    {
        // user dragged towards the left
        counter--;
    }
}

顺便说一下:我在大约3分钟前才知道这种方法。谷歌的第一个搜索结果是官方的苹果文档。


速度为0.0并不代表任何方向,因此更适合明确指定(如果vel.x> 0.0 else if vel.x < 0.0)。 - agandi

39

像这样的东西:

- (void)pan:(UIPanGestureRecognizer *)sender
{

    typedef NS_ENUM(NSUInteger, UIPanGestureRecognizerDirection) {
        UIPanGestureRecognizerDirectionUndefined,
        UIPanGestureRecognizerDirectionUp,
        UIPanGestureRecognizerDirectionDown,
        UIPanGestureRecognizerDirectionLeft,
        UIPanGestureRecognizerDirectionRight
    };

    static UIPanGestureRecognizerDirection direction = UIPanGestureRecognizerDirectionUndefined;

    switch (sender.state) {

        case UIGestureRecognizerStateBegan: {

            if (direction == UIPanGestureRecognizerDirectionUndefined) {

                CGPoint velocity = [sender velocityInView:recognizer.view];

                BOOL isVerticalGesture = fabs(velocity.y) > fabs(velocity.x);

                if (isVerticalGesture) {
                    if (velocity.y > 0) {
                        direction = UIPanGestureRecognizerDirectionDown;
                    } else {
                        direction = UIPanGestureRecognizerDirectionUp;
                    }
                }

                else {
                    if (velocity.x > 0) {
                        direction = UIPanGestureRecognizerDirectionRight;
                    } else {
                        direction = UIPanGestureRecognizerDirectionLeft;
                    }
                }
            }

            break;
        }

        case UIGestureRecognizerStateChanged: {
            switch (direction) {
                case UIPanGestureRecognizerDirectionUp: {
                    [self handleUpwardsGesture:sender];
                    break;
                }
                case UIPanGestureRecognizerDirectionDown: {
                    [self handleDownwardsGesture:sender];
                    break;
                }
                case UIPanGestureRecognizerDirectionLeft: {
                    [self handleLeftGesture:sender];
                    break;
                }
                case UIPanGestureRecognizerDirectionRight: {
                    [self handleRightGesture:sender];
                    break;
                }
                default: {
                    break;
                }
            }
        }

        case UIGestureRecognizerStateEnded: {
            direction = UIPanGestureRecognizerDirectionUndefined;   
            break;
        }

        default:
            break;
    }

}

- (void)handleUpwardsGesture:(UIPanGestureRecognizer *)sender
{
    NSLog(@"Up");
}

- (void)handleDownwardsGesture:(UIPanGestureRecognizer *)sender
{
    NSLog(@"Down");
}

- (void)handleLeftGesture:(UIPanGestureRecognizer *)sender
{
    NSLog(@"Left");
}

- (void)handleRightGesture:(UIPanGestureRecognizer *)sender
{
    NSLog(@"Right");
}

21

我之前的Swift回答

public enum Direction: Int {
    case Up
    case Down
    case Left
    case Right

    public var isX: Bool { return self == .Left || self == .Right }
    public var isY: Bool { return !isX }
}

public extension UIPanGestureRecognizer {

    public var direction: Direction? {
        let velocity = velocityInView(view)
        let vertical = fabs(velocity.y) > fabs(velocity.x)
        switch (vertical, velocity.x, velocity.y) {
        case (true, _, let y) where y < 0: return .Up
        case (true, _, let y) where y > 0: return .Down
        case (false, let x, _) where x > 0: return .Right
        case (false, let x, _) where x < 0: return .Left
        default: return nil
        }
    }
}

我相信你颠倒了.Up和.Down,但除此之外,非常感谢!你是救命恩人! - Kiley
@AdamWaite:更新答案为@available(swift,deprecated: 4.2, renamed: "abs") public func fabs<T> (_ x: T) -> T where T: FloatingPoint - Abhishek Thapliyal

21

这是一个经过清理的 Swift 5 版本,附带使用示例:

public enum PanDirection: Int {
    case up, down, left, right
    public var isVertical: Bool { return [.up, .down].contains(self) }
    public var isHorizontal: Bool { return !isVertical }
}

public extension UIPanGestureRecognizer {

   var direction: PanDirection? {
        let velocity = self.velocity(in: view)
        let isVertical = abs(velocity.y) > abs(velocity.x)
        switch (isVertical, velocity.x, velocity.y) {
        case (true, _, let y) where y < 0: return .up
        case (true, _, let y) where y > 0: return .down
        case (false, let x, _) where x > 0: return .right
        case (false, let x, _) where x < 0: return .left
        default: return nil
        }
    }

}

@IBAction func pan(_ recognizer: UIPanGestureRecognizer) {        
    if let direction = recognizer.direction {
        if direction.isVertical {
            //do what you want when pan is vertical
        } else if direction == .left {
            //do what you want when pan is left
        }
    }
}

也许最好做一些更改,以避免需要返回一个选项,比如在“where”中使用> =。我想不出任何情况下一个手势会返回“无方向”。 - macabeus

2
请注意,Adam提供的Swift版本存在一个错误。.up应该在y < 0时返回,而.down应该在y > 0时返回(它们被颠倒了)。
因此:
//MARK: - Direction
internal enum Direction {
    case up
    case down
    case left
    case right
}

//MARK: - UIPanGestureRecognizer
internal extension UIPanGestureRecognizer {
    internal var direction: Direction? {
        let velocity = velocityInView(view)
        let isVertical = fabs(velocity.y) > fabs(velocity.x)

        switch (isVertical, velocity.x, velocity.y) {
            case (true, _, let y) where y < 0: return .up
            case (true, _, let y) where y > 0: return .down
            case (false, let x, _) where x > 0: return .right
            case (false, let x, _) where x < 0: return .left
            default: return nil
        }
    }
}

一个Swift的新手在这里,假设我想在向上滑动时调用一个方法,我该怎么做?@Justin Stanley - Alejandro Vargas
添加您的平移手势识别器,并在其中一个平移代理中执行以下操作:如果recognizer.direction == .up { do something } 或者在recognizer.direction上进行switch并将其放入.case for .up。 - Justin Stanley

2
重写Adam Waite的Swift 3版本
public enum PanDirection: Int {
    case up,
    down,
    left,
    right

    public var isX: Bool {
        return self == .left || self == .right
    }

    public var isY: Bool {
        return !isX
    }
}

extension UIPanGestureRecognizer {
    var direction: PanDirection? {
        let velocity = self.velocity(in: view)
        let vertical = fabs(velocity.y) > fabs(velocity.x)
        switch (vertical, velocity.x, velocity.y) {
        case (true, _, let y):
            return y < 0 ? .up : .down

        case (false, let x, _):
            return x > 0 ? .right : .left
        }
    }
}

这个版本存在一个关键性的错误。如果y == 0,它将返回.down。而如果x == 0,它将返回.left。在这些情况下,你应该返回一个nil PanDirection,就像Adam Waite的版本一样。 - Elijah
我认为在 direction 中不需要返回一个选项。 - macabeus

0

从我的角度来看,velocity 太过敏感了,用户很容易将轻触屏幕与滑动小部分混淆。

因此我使用自定义的 UIPanGestureRecognizerSwift 5

enum Orientation{
    case lhs, rhs, `default`
}



class HPan: UIPanGestureRecognizer {

    var first: CGPoint?
    var orient = .default

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

        state = .began
        first = touches.first?.location(in: view?.window)
    }


    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)
        if state == .cancelled{
            return
        }
        if let location = touches.first?.location(in: view?.window), let begin = first{
          let magicDistance: CGFloat = 20
          if location.x - begin.x > magicDistance{
               orient = .rhs
               state = .ended
          }  
          else if begin.x - location.x > magicDistance{
               orient = .lhs
               state = .ended
          }  
        }

    }



    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesEnded(touches, with: event)
        state = .ended
    }



    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesCancelled(touches, with: event)
        state = .cancelled
    }


    override func reset() {
        super.reset()

        state = .possible
        isRight = false
        orient = .default
    }
}

调用方式如下:

@objc func push(_ gesture: RightPan){
        if gesture.state == UIGestureRecognizer.State.ended{
            switch gesture.orient{
                case .lhs:
                     counter -= 1
                case .rhs:
                     counter += 1
                default:
                    ()        
            }
        }

    }

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