UIPanGestureRecognizer - Only vertical or horizontal

IosUigesturerecognizer

Ios Problem Overview


I have a view that has a UIPanGestureRecognizer to drag the view vertically. So in the recognizer callback, I only update the y-coordinate to move it. The superview of this view, has a UIPanGestureRecognizer that will drag the view horizontally, just updating the x-coordinate.

The problem is that the first UIPanGestureRecognizer is taking the event to move the view vertically, so I can not use the superview gesture.

I have tried

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

and both will work, but I don't want that. I want the horizontally to be detected only if the movement is clearly horizontal. So it would be great if the UIPanGestureRecognizer had a direction property.

How can I achieve this behavior? I find the docs very confusing, so maybe someone can explain it better here.

Ios Solutions


Solution 1 - Ios

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

Solution 2 - Ios

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

Example of usage:

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

Solution 3 - Ios

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.

Solution 4 - Ios

I tried to constrain the valid area horizontally with 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;
}

Then, only swiping within thresholdAngle degree horizontally can trigger this pan gesture.

Solution 5 - Ios

Swift 3.0 answer: just handles does the vertical gesture

    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

}

Solution 6 - Ios

The following solution solved my problem:

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

This is actually just check if pan is going on main view or tableView.

Solution 7 - Ios

Swift 3 version of Lee's answer for the lazy

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

Solution 8 - Ios

I took Lee Goodrich's answer and extended it as I needed specifically a single direction pan. Use it like this: let pan = PanDirectionGestureRecognizer(direction: .vertical(.up), target: self, action: #selector(handleCellPan(_:)))

I also added some commenting to make it a little clearer what decisions are actually being made.

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

Solution 9 - Ios

Swift 4.2

The solution is just for only support pan gesture vertically, same as horizontal.

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

Solution 1:

@objc func panAction(pan: UIPanGestureRecognizer) {

        let velocity = pan.velocity(in: panView)
        guard abs(velocity.y) > abs(velocity.x) else {
            return
        }
}

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

Then the swipe gesture will swallow the pan gesture. Of course, you don't need to do anything in swipeAction.

Solution 10 - Ios

Here is a custom pan gesture in Swift 5

U can constraint its direction and the max angle in the direction, you can also constraint its minimum speed in the direction.

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

call like this:

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

or

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

Solution 11 - Ios

You can find the direction dragging on UIView through UIPanGestureRecognizer. Please follow the code.

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

Solution 12 - Ios

Here is how I resolved:

First I enabled Simultaneously PanGesture Recognition.

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

return YES;

Then I Isolate Horizontal and Vertical Pan gestures (accumulator is NSMutableArray property):

- (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];

I pushed an example here:

add custom pan in scrollview

Solution 13 - Ios

You may use simple panGestureRecognizer. No need to use pandirectionregognizer or stuff. Just use y value of translationInview Below code move drag view only up and down

- (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];
    }
}

Solution 14 - Ios

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 
}

Solution 15 - Ios

- (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];}

I created PanGestureRecognizer @selector action method for the object that needed only Horizontal scrolling.

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

Solution 16 - Ios

Swift way

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
}

Solution 17 - Ios

For all you Swift users out there, this will do the job :)

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
}




}

Solution 18 - Ios

I took an excellent answer by Lee Goodrich and ported to 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
                
            }
            
        }
    }
}

Solution 19 - Ios

I would love to share my approach because all other approaches are based on either UIGestureRecognizerDelegate or subclassing UIPanGestureRecognizer.

My approach is based on runtime and swizzling. I'm not 100% sure about this approach, but you can test and improve it yourself.

Set the direction of any UIPanGestureRecognizer with just one line of code:

UITableView().panGestureRecognizer.direction = UIPanGestureRecognizer.Direction.vertical

use pod 'UIPanGestureRecognizerDirection' or the code:

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

Solution 20 - Ios

I tried this: which worked for me as per the question describes

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

Solution 21 - Ios

SWIFT 4.2

I went further and make a direction Pan Gesture:

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

(Used: https://github.com/fastred/SloppySwiper and https://stackoverflow.com/a/30607392/5790492)

Solution 22 - Ios

PanGestureRecognizer interface contains the following definitions:

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

I didn't check this, but maybe it's accessible via subclass.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLocoMikeView Question on Stackoverflow
Solution 1 - IosHejaziView Answer on Stackoverflow
Solution 2 - IosLee GoodrichView Answer on Stackoverflow
Solution 3 - IosLocoMikeView Answer on Stackoverflow
Solution 4 - IosStoryView Answer on Stackoverflow
Solution 5 - IosSiavash AlpView Answer on Stackoverflow
Solution 6 - IosBorut TomazinView Answer on Stackoverflow
Solution 7 - IosCesar VarelaView Answer on Stackoverflow
Solution 8 - IosRob BoothView Answer on Stackoverflow
Solution 9 - IosWilliam HuView Answer on Stackoverflow
Solution 10 - IosdengST30View Answer on Stackoverflow
Solution 11 - IosArunjackView Answer on Stackoverflow
Solution 12 - Iosuser2224444View Answer on Stackoverflow
Solution 13 - IosAdd080bbAView Answer on Stackoverflow
Solution 14 - IosSaumyaView Answer on Stackoverflow
Solution 15 - IosRatzView Answer on Stackoverflow
Solution 16 - IosAdam SmakaView Answer on Stackoverflow
Solution 17 - IosPhilView Answer on Stackoverflow
Solution 18 - Iosaverage JoeView Answer on Stackoverflow
Solution 19 - IosiWheelBuyView Answer on Stackoverflow
Solution 20 - IosAadi007View Answer on Stackoverflow
Solution 21 - IosNike KovView Answer on Stackoverflow
Solution 22 - IoszxcatView Answer on Stackoverflow