UIPanGestureRecognizer - Only vertical or horizontal UIPanGestureRecognizer - Only vertical or horizontal ios ios

UIPanGestureRecognizer - Only vertical or horizontal


Just do this for the vertical pan gesture recognizer, it works for me:

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

And for Swift:

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


I created a solution with subclassing like in the answer @LocoMike provided, but used the more effective detection mechanism via initial velocity as provided by @Hejazi. I'm also using Swift, but this should be easy to translate to Obj-C if desired.

Advantages over other solutions:

  • Simpler and more concise than other subclassing solutions. No additional state to manage.
  • Direction detection happens prior to sending Began action, so your pan gesture selector receives no messages if the wrong direction is swiped.
  • After initial direction is determined, direction logic is no longer consulted. This results in the generally desired behavior of activating your recognizer if the initial direction is correct, but does not cancel the gesture after it has begun if a user's finger doesn't travel perfectly along the direction.

Here's the code:

import UIKit.UIGestureRecognizerSubclassenum 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            }        }    }}

Example of usage:

let panGestureRecognizer = PanDirectionGestureRecognizer(direction: .horizontal, target: self, action: #selector(handlePanGesture(_:)))panGestureRecognizer.cancelsTouchesInView = falseself.view.addGestureRecognizer(panGestureRecognizer)func handlePanGesture(_ pan: UIPanGestureRecognizer) {    let percent = max(pan.translation(in: view).x, 0) / view.frame.width    switch pan.state {    case .began:    ...}


I figured it out creating a subclass of 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

This will only trigger the gesture if the user starts dragging in the selected behavior. Set the direction property to a correct value and you are all set.