UIPanGestureRecognizer – 只有垂直或水平
我有一个视图,有一个UIPanGestureRecognizer
垂直拖动视图。 所以在识别器callback中,我只更新y坐标来移动它。 这个视图的UIPanGestureRecognizer
视图有一个UIPanGestureRecognizer
,它将水平拖动视图,只是更新x坐标。
问题是第一个UIPanGestureRecognizer
正在使事件垂直移动视图,所以我不能使用超级视图手势。
我努力了
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer *)otherGestureRecognizer;
两者都可以工作,但是我不想那样做。 只有当运动明显水平时,才需要水平检测。 所以如果UIPanGestureRecognizer
具有方向属性,那将是非常好的。
我怎样才能做到这一点? 我发现文档非常混乱,所以也许有人可以在这里更好地解释它。
只要做这个垂直平移手势识别器,它适用于我:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)panGestureRecognizer { CGPoint velocity = [panGestureRecognizer velocityInView:someView]; return fabs(velocity.y) > fabs(velocity.x); }
我想出了创buildUIPanGestureRecognizer的子类
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
这只会在用户开始拖动所选行为时触发手势。 将方向属性设置为正确的值,然后全部设置。
我在@LocoMike提供的答案中创build了一个子类化的解决scheme,但通过@Hejazi提供的初始速度使用了更有效的检测机制。 我也使用Swift,但是如果需要的话,这应该很容易翻译成Obj-C。
优于其他解决scheme:
- 比其他子类解决scheme更简单,更简洁。 没有额外的状态来pipe理。
- 方向检测发生在发送开始动作之前,所以如果滑动错误的方向,则您的平移手势select器不会收到消息。
- 在确定了初始方向之后,不再咨询方向逻辑。 如果初始方向是正确的,则这导致激活识别器的通常希望的行为,但是如果用户的手指没有沿着方向完美地行进,则在手势开始之后不会取消该手势。
代码如下:
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 } } } }
我尝试用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; }
然后,只在水平的阈值angular度内轻扫可触发此平移手势。
以下解决scheme解决了我的问题:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if ([gestureRecognizer.view isEqual:self.view] && [otherGestureRecognizer.view isEqual:self.tableView]) { return NO; } return YES; }
这实际上只是检查是否泛在主视图或tableView。
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 }
斯威夫特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 } } } }
你可以通过UIPanGestureRecognizer
find在UIView
上拖动的方向。 请按照代码。
- (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; }
你可以使用简单的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]; } }
这是我如何解决:
首先,我同时启用了PanGesture识别。
-(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];
我在这里推了一个例子:
在滚动视图中添加自定义平移
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 }
- (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];}
我为只需要水平滚动的对象创build了PanGestureRecognizer @selector动作方法。
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(smileyDragged:)]; [buttonObject addGestureRecognizer:gesture];
对于所有你在那里的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 } }
我由Lee Goodrich做了一个很好的回答 ,并把它移植到了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 } } } }
迅速的方式
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.x) > fabs(translation.y) { return true } return false }
我很想分享我的方法,因为所有其他方法都基于UIGestureRecognizerDelegate
或UIPanGestureRecognizer
子类。
我的方法是基于运行时和调整。 我不是100%肯定这种方法,但你可以自己testing和改进它。
用一行代码设置任何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) } }
我拿了李古德里奇的回答,并把它扩大,因为我特别需要一个方向锅。 像这样使用它: 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 } } } } }
PanGestureRecognizer
接口包含以下定义:
unsigned int _canPanHorizontally:1; unsigned int _canPanVertically:1;
我没有检查这个,但也许可以通过子类访问。