【问题标题】:UIPanGestureRecognizer - Only vertical or horizontalUIPanGestureRecognizer - 仅垂直或水平
【发布时间】:2011-10-29 09:01:56
【问题描述】:

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

问题是第一个UIPanGestureRecognizer正在接受事件来垂直移动视图,所以我不能使用superview手势。

我试过了

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
 shouldRecognizeSimultaneouslyWithGestureRecognizer:
                            (UIGestureRecognizer *)otherGestureRecognizer;

两者都可以,但我不希望这样。我希望只有在运动明显水平时才能检测到水平方向。因此,如果UIPanGestureRecognizer 具有方向属性,那就太好了。

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

【问题讨论】:

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

标签: ios uigesturerecognizer


【解决方案1】:

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

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

对于 Swift:

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIPanGestureRecognizer) -> Bool {
    let velocity = gestureRecognizer.velocity(in: someView)
    return abs(velocity.x) > abs(velocity.y)
}

【讨论】:

  • 试过这个,但是翻译经常是== (0,0),所以不准确
  • 使用velocityInView:代替translationInView:时(0,0)问题不明显。
  • @cbh2000 我将答案更新为使用velocityInView 而不是translationInView
  • @JoeBlow UISwipeGestureRecognizer 是一种触发转换以响应滑动手势的简单方法,但它是一种离散手势。如果有人正在寻找一种连续的方法——比如用手势动画过渡——UIPanGestureRecognizer 就是要走的路。
  • 这是聪明的解决方案
【解决方案2】:

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

相对于其他解决方案的优势:

  • 比其他子类化解决方案更简单、更简洁。无需管理其他状态。
  • 方向检测发生在发送 Began 操作之前,因此如果滑动错误的方向,您的平移手势选择器不会收到任何消息。
  • 确定初始方向后,不再参考方向逻辑。这会导致在初始方向正确时激活识别器的一般期望行为,但如果用户的手指没有沿着该方向完美移动,则不会在开始后取消手势。

代码如下:

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

使用示例:

let panGestureRecognizer = PanDirectionGestureRecognizer(direction: .horizontal, target: self, action: #selector(handlePanGesture(_:)))
panGestureRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(panGestureRecognizer)

func handlePanGesture(_ pan: UIPanGestureRecognizer) {
    let percent = max(pan.translation(in: view).x, 0) / view.frame.width

    switch pan.state {
    case .began:
    ...
}

【讨论】:

  • 这绝对是最好的答案。可惜苹果没有在UIPanGestureRecognizer 中添加这样的功能。
  • 你能提供一个使用例子吗?
  • 这太可爱了!谢谢!在水平和垂直堆叠时完美运行:let horizontalPanRecognizer = PanDirectionGestureRecognizer(direction: .horizontal, target: self, action: #selector(handleHorizontalPanGesture(recognizer:))) self.view?.addGestureRecognizer(horizontalPanRecognizer); let verticalPanRecognizer = PanDirectionGestureRecognizer(direction: .vertical, target: self, action: #selector(handleVerticalPanGesture(recognizer:))) self.view?.addGestureRecognizer(verticalPanRecognizer);
  • 哦,这太棒了!谢谢!
【解决方案3】:

我发现它创建了一个 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

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

这只会在用户开始拖动所选行为时触发手势。将方向属性设置为正确的值,一切就绪。

【讨论】:

  • 我认为'reset'最初没有被调用。添加了一个initWithTarget:action: 方法并调用了reset,一切都很好。
  • 在当前实现中DirectionPanGestureRecognizer 将忽略快速拖动,除非您设置kDirectionPanThreshold = 20 左右,在这种情况下它可能会发出错误警报。我建议使用abs(_moveX) &gt; abs(_moveY) 而不是abs(_moveX) &gt; kDirectionPanThreshold 并分别更改水平大小写。
  • 我应该添加这个,这对我也有帮助,但是我必须添加以触发平移手势识别器的是 if 的 else 部分,在 _drag = YES I 行下已添加self.state = UIGestureRecognizerStateChanged;
【解决方案4】:

我尝试使用 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) > thresholdAngle) {
            return NO;
        }
    }
    return YES;
}

那么,只有在 thresholdAngle 度范围内水平滑动才能触发此平移手势。

【讨论】:

  • 很好的答案。当我混合 UIScrollView 手势和常规手势时,这真的帮助了我。我认为这个例子的意思是说“thresholdAngle”而不是“enableThreshold”。你应该很少使用 atan() 因为它可以产生一个 NAN。请改用 atan2()。
【解决方案5】:

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

}

【讨论】:

    【解决方案6】:

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

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

    这实际上只是检查 pan 是在主视图还是 tableView 上。

    【讨论】:

    • 为什么要调用 -isEqual: 来比较两个视图是否相同?一个简单的身份检查就足够了。手势识别器.view == self.view
    【解决方案7】:

    Swift 3 版本的 Lee 对懒人的回答

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

    【讨论】:

      【解决方案8】:

      我采用了Lee Goodrichanswer 并扩展了它,因为我特别需要一个单向平移。像这样使用它:let pan = PanDirectionGestureRecognizer(direction: .vertical(.up), target: self, action: #selector(handleCellPan(_:)))

      我还添加了一些评论,以便更清楚地了解实际做出的决定。

      import UIKit.UIGestureRecognizerSubclass
      
      enum PanVerticalDirection {
          case either
          case up
          case down
      }
      
      enum PanHorizontalDirection {
          case either
          case left
          case right
      }
      
      enum PanDirection {
          case vertical(PanVerticalDirection)
          case horizontal(PanHorizontalDirection)
      }
      
      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 {
      
                  // expecting horizontal but moving vertical, cancel
                  case .horizontal(_) where fabs(vel.y) > fabs(vel.x):
                      state = .cancelled
      
                  // expecting vertical but moving horizontal, cancel
                  case .vertical(_) where fabs(vel.x) > fabs(vel.y):
                      state = .cancelled
      
                  // expecting horizontal and moving horizontal
                  case .horizontal(let hDirection):
                      switch hDirection {
      
                          // expecting left but moving right, cancel
                          case .left where vel.x > 0: state = .cancelled
      
                          // expecting right but moving left, cancel
                          case .right where vel.x < 0: state = .cancelled
                          default: break
                      }
      
                  // expecting vertical and moving vertical
                  case .vertical(let vDirection):
                      switch vDirection {
                          // expecting up but moving down, cancel
                          case .up where vel.y > 0: state = .cancelled
      
                          // expecting down but moving up, cancel
                          case .down where vel.y < 0: state = .cancelled
                          default: break
                      }
                  }
              }
          }
      }
      

      【讨论】:

      • override func touchesMoved - Method does not override any method from its superclass 中的错误。
      • @Annjawn 你必须使用“import UIKit.UIGestureRecognizerSubclass”
      • 好的。我没有意识到这一点。我认为 import UIKit 会自动导入它。我试试看。
      【解决方案9】:

      斯威夫特 4.2

      解决方案仅支持垂直平移手势,与水平相同。

      let pan = UIPanGestureRecognizer(target: self, action: #selector(test1))
      pan.cancelsTouchesInView = false
      panView.addGestureRecognizer(pan)
      

      解决方案 1

      @objc func panAction(pan: UIPanGestureRecognizer) {
      
              let velocity = pan.velocity(in: panView)
              guard abs(velocity.y) > abs(velocity.x) else {
                  return
              }
      }
      

      解决方案 2:

        [UISwipeGestureRecognizer.Direction.left, .right].forEach { direction in
              let swipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction))
              swipe.direction = direction
              panView.addGestureRecognizer(swipe)
              pan.require(toFail: swipe)
          }
      

      然后滑动手势将吞噬平移手势。当然,你不需要在swipeAction做任何事情。

      【讨论】:

        【解决方案10】:

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

        这样调用:

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

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

        【讨论】:

        • 完美答案。谢谢
        【解决方案11】:

        您可以在UIViewUIPanGestureRecognizer 上找到拖动方向。请按照代码进行操作。

         - (void)viewDidLoad {
            [super viewDidLoad];
            flipFoward = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(doFlipForward:)];
            [flipFoward setMaximumNumberOfTouches:1];
            [flipFoward setMinimumNumberOfTouches:1];
            [flipFoward setDelegate:self];
            [self.view addGestureRecognizer:flipFoward];
            flipBack = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(doFlipBack:)];
            [flipBack setMaximumNumberOfTouches:1];
            [flipBack setMinimumNumberOfTouches:1];
            [flipBack setDelegate:self];
            [self.view addGestureRecognizer:flipBack];
        }
        
        #pragma mark -
        #pragma mark RESPONDER
        
        -(void)doFlipForward:(UIGestureRecognizer *)aGestureRecognizer{
            NSLog(@"doFlipForward");
            if([(UIPanGestureRecognizer*)aGestureRecognizer state] == UIGestureRecognizerStateBegan) {
                NSLog(@"UIGestureRecognizerStateBegan");
            }
            if([(UIPanGestureRecognizer*)aGestureRecognizer state] == UIGestureRecognizerStateChanged) {
                NSLog(@"UIGestureRecognizerStateChanged");
            }
            if([(UIPanGestureRecognizer*)aGestureRecognizer state] == UIGestureRecognizerStateEnded) {
                NSLog(@"UIGestureRecognizerStateEnded");
            }
        }
        
        -(void)doFlipBack:(UIGestureRecognizer *)aGestureRecognizer{
            NSLog(@"doFlipBack");
            if([(UIPanGestureRecognizer*)aGestureRecognizer state] == UIGestureRecognizerStateBegan) {
                NSLog(@"UIGestureRecognizerStateBegan1");
            }
            if([(UIPanGestureRecognizer*)aGestureRecognizer state] == UIGestureRecognizerStateChanged) {
                NSLog(@"UIGestureRecognizerStateChanged1");
            }
            if([(UIPanGestureRecognizer*)aGestureRecognizer state] == UIGestureRecognizerStateEnded) {
                NSLog(@"UIGestureRecognizerStateEnded1");
            }
        }
        
        #pragma mark -
        #pragma mark DELEGATE
        
        -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
            CGSize size = [self.view bounds].size;
            CGFloat touchX = [gestureRecognizer locationInView:self.view].x;
            if((gestureRecognizer == flipFoward) 
               && touchX >= (size.width - 88.0f))
            {
                return YES;
            }
            if((gestureRecognizer == flipBack)
               && touchX <= 88.0f)
            {
                return YES;
            }
            return NO;
        }
        

        【讨论】:

        • 实际上这不是一个好的解决方案,因为只有左边的 88 个点才能平移。
        【解决方案12】:

        这是我的解决方法:

        首先我启用了同时泛手势识别。

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

        然后我隔离水平和垂直平移手势(累加器是 NSMutableArray 属性):

        - (void)verticalPan :(UIPanGestureRecognizer *) sender {
        
        CGPoint touch  = [sender translationInView:self];
        NSValue *value = [NSValue valueWithCGPoint:touch];
        [accumulator addObject:value];
        
        int firstXObjectValue = (int)[[accumulator objectAtIndex:0] CGPointValue].x ;
        int lastXObjectValue =  (int)[[accumulator lastObject] CGPointValue].x;
        
        int firstYObjectValue = (int)[[accumulator objectAtIndex:0] CGPointValue].y;
        int lastYObjectValue =  (int)[[accumulator lastObject] CGPointValue].y;
        
        if (abs(lastYObjectValue - firstYObjectValue) < 4 && abs(lastXObjectValue - firstXObjectValue) > 4) {
            NSLog(@"Horizontal Pan");
        
            //do something here
        }
        else if (abs(lastYObjectValue - firstYObjectValue) > 4 && abs(lastXObjectValue - firstXObjectValue) < 4){
            NSLog(@"Vertical Pan");
        
            //do something here
        }
        
        if (accumulator.count > 3)
            [accumulator removeAllObjects];
        

        我在这里推了一个例子:

        add custom pan in scrollview

        【讨论】:

          【解决方案13】:
          let pangesture = UIPanGestureRecognizer(target: self, action: "dragview:")
          yourview.addGestureRecognizer(pangesture)
          
          
          func dragview(panGestureRecognizer:UIPanGestureRecognizer)
          {
              let touchlocation = panGestureRecognizer.locationInView(parentview)
              yourview.center.y = touchlocation.y //x for horizontal 
          }
          

          【讨论】:

            【解决方案14】:

            您可以使用简单的panGestureRecognizer。无需使用 pandirectionregognizer 或其他东西。只需使用 translationInview 的 y 值 下面的代码只上下移动拖动视图

            - (void)gesturePan_Handle:(UIPanGestureRecognizer *)gesture {
                if (gesture.state == UIGestureRecognizerStateChanged) {
                    CGPoint translation = [gesture translationInView:gesture.view];
                    recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
                    [gesture setTranslation:CGPointMake(0, 0) inView:gesture.view];
                }
            }
            

            【讨论】:

            • 此代码只是平移视图。没有实现方向锁定。
            【解决方案15】:
            - (void)dragAction:(UIPanGestureRecognizer *)gesture{
                  UILabel *label = (UILabel *)gesture.view;
                  CGPoint translation = [gesture translationInView:label];
                 label.center = CGPointMake(label.center.x + translation.x,
                                         label.center.y + 0);
                [gesture setTranslation:CGPointZero inView:label];}
            

            我为只需要水平滚动的对象创建了 PanGestureRecognizer @selector 操作方法。

             UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(smileyDragged:)];
                [buttonObject addGestureRecognizer:gesture];
            

            【讨论】:

              【解决方案16】:

              快捷方式

              override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
                  if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer {
                      return isVerticalGesture(panGestureRecognizer)
                  }
                  return false
              }
              
              private func isVerticalGesture(_ recognizer: UIPanGestureRecognizer) -> Bool {
                  let translation = recognizer.translation(in: superview!)
                  if fabs(translation.y) > fabs(translation.x) {
                      return true
                  }
                  return false
              }
              

              【讨论】:

                【解决方案17】:

                对于所有 Swift 用户来说,这将完成这项工作 :)

                import Foundation
                import UIKit.UIGestureRecognizerSubclass
                
                
                class DirectionPanGestureRecognizer: UIPanGestureRecognizer {
                
                let kDirectionPanThreshold = CGFloat(5)
                var drag = true
                var moveX = CGFloat(0)
                var moveY = CGFloat(0)
                
                override init(target: AnyObject, action: Selector) {
                    super.init(target: target, action: action)
                }
                
                override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
                    super.touchesMoved(touches, withEvent: event)
                    if state == .Failed {
                        return
                    }
                
                    let nowPoint = touches.anyObject()?.locationInView(view)
                    let prevPoint = touches.anyObject()?.previousLocationInView(view)
                    moveX += prevPoint!.x - nowPoint!.x
                    moveY += prevPoint!.y - nowPoint!.y
                    if !drag {
                        if abs(moveX) > kDirectionPanThreshold {
                            state = .Failed
                        } else {
                            drag = true
                        }
                
                    }
                
                }
                
                 override func reset() {
                    super.reset()
                    moveX = 0
                    moveY = 0
                    drag = false
                }
                
                
                
                
                }
                

                【讨论】:

                  【解决方案18】:

                  我采用了 Lee Goodrich 的出色 answer 并移植到 Swift 3

                  import UIKit
                  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: 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
                  
                              }
                  
                          }
                      }
                  }
                  

                  【讨论】:

                    【解决方案19】:

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

                    我的方法基于运行时和 swizzling。我不能 100% 确定这种方法,但您可以自己测试和改进它。

                    只需一行代码即可设置任意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)
                        }
                    }
                    

                    【讨论】:

                      【解决方案20】:

                      我试过这个:根据问题描述,这对我有用

                      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
                          if gestureRecognizer is UIPanGestureRecognizer {
                              return true
                          } else {
                              return false
                          }
                      }
                      

                      【讨论】:

                        【解决方案21】:

                        SWIFT 4.2

                        我更进一步,做了一个方向泛手势:

                        enum PanDirection {
                            case up
                            case left
                            case right
                            case down
                        }
                        
                        class PanDirectionGestureRecognizer: UIPanGestureRecognizer {
                            
                            fileprivate 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)
                                
                                guard state != .failed else { return }
                        
                                let vel = velocity(in: view)
                        
                                let velocities: [PanDirection: CGFloat]
                                    = [.up: -vel.y,
                                       .left: -vel.x,
                                       .right: vel.x,
                                       .down: vel.y]
                        
                                let sortedKeys = velocities.sorted { $0.1 < $1.1 }
                        
                                if let key = sortedKeys.last?.key,
                                    key != direction {
                                    state = .cancelled
                                }
                            }
                        }
                        

                        (使用:https://github.com/fastred/SloppySwiperhttps://stackoverflow.com/a/30607392/5790492

                        【讨论】:

                          【解决方案22】:

                          PanGestureRecognizer接口包含以下定义:

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

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

                          【讨论】:

                          • 看起来很有希望,但该 API 并未公开。使用私有 API 通常会被 Apple 拒绝。