UIPanGestureRecognizer - только вертикальный или горизонтальный
у меня есть вид, который имеет UIPanGestureRecognizer
для перетащите окно по вертикали. Поэтому в обратном вызове распознавателя я только обновляю координату y, чтобы переместить ее. Супервизор этого представления, имеет UIPanGestureRecognizer
это будет перетаскивать вид по горизонтали, просто обновляя координату x.
проблема в том, что первый UIPanGestureRecognizer
это мероприятие двигаться по вертикали, поэтому я не могу использовать этот жест суперпанель.
Я пробовал
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer;
и оба будут работать, но я не хотеть этого. Я хочу, чтобы горизонталь была обнаружена только в том случае, если движение четко горизонтально. Так что было бы здорово, если бы UIPanGestureRecognizer
имел свойство направления.
как я могу добиться этого поведения? Я нахожу документы очень запутанными, поэтому, возможно, кто-то может объяснить это лучше здесь.
19 ответов:
просто сделайте это для распознавателя жестов вертикального панорамирования, он работает для меня:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)panGestureRecognizer { CGPoint velocity = [panGestureRecognizer velocityInView:someView]; return fabs(velocity.y) > fabs(velocity.x); }
Я создал решение с подклассами, как в ответе @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 } } } }
Я понял, что это создание подкласса 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.м:
#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
это вызовет жест только в том случае, если пользователь начнет перетаскивать выбранное поведение. Установите для свойства direction правильное значение, и все готово.
Я попытался ограничить допустимую область по горизонтали с помощью 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; }
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 }
следующее решение решило мою проблему:
- (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 версия ответа ли для ленивых
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 } } } }
вы можете найти направление перетаскивания на
UIView
черезUIPanGestureRecognizer
. Пожалуйста, код.- (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
или вещи. Просто используйте значение ytranslationInview
Ниже кода переместить перетащить вид только вверх и вниз- (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]; } }
вот как я решил:
сначала я включил одновременно распознавание Пангестуры.
-(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];}
Я создал метод действия PanGestureRecognizer @selector для объекта, который нуждался только в горизонтальной прокрутке.
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(smileyDragged:)]; [buttonObject addGestureRecognizer:gesture];
взял Ли Гудрич ' s ответ и расширил его, поскольку мне нужен был специально один пан направления. Используйте его так:
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 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 }
для всех вас 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 } }
Я взял отличный ответ ли Гудрич и портирован на 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 } } } }
Я хотел бы поделиться своим подходом, потому что все другие подходы основаны на любом
UIGestureRecognizerDelegate
или подклассыUIPanGestureRecognizer
.мой подход основан на runtime и 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({ (.location(in: .view) - .previousLocation(in: .view)).direction }) .filter({ != 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) } }
я попробовал это: который работал для меня в соответствии с вопросом описывает
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer is UIPanGestureRecognizer { return true } else { return false } }