UIPanGestureRecognizer-세로 또는 가로 만


146

나는이 가지고있는보기가 UIPanGestureRecognizer수직으로 뷰를 드래그합니다. 인식기 콜백에서는 y 좌표 만 업데이트하여 이동합니다. 이 뷰의 슈퍼 뷰 UIPanGestureRecognizer에는 뷰를 가로로 드래그하여 x 좌표 만 업데이트하는 뷰가 있습니다.

문제는 첫 번째 UIPanGestureRecognizer로 뷰를 세로로 이동하는 이벤트를 수행하므로 슈퍼 뷰 제스처를 사용할 수 없다는 것입니다.

나는 시도했다

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

둘 다 작동하지만, 나는 그것을 원하지 않습니다. 움직임이 명확하게 수평 인 경우에만 수평을 감지하고 싶습니다. UIPanGestureRecognizer방향 속성이 있으면 좋을 것 입니다.

이 동작을 어떻게 달성 할 수 있습니까? 문서가 매우 혼란 스럽기 때문에 누군가가 여기에서 더 잘 설명 할 수 있습니다.


해결책을 찾은 경우 자신의 질문에 대답하고 대답을 수락해도됩니다.
— jtbandes

@JoeBlow 정말? 스 와이프 제스처 카테고리를 만들어 제스처의 번역 및 속도를 수신 했습니까?
— Roman Truba

2
나는 당신이 무슨 말을하는지 이해하지 못합니다. 가로 스 와이프 를 감지 하려면 운영 체제에 완전히 내장되어 있습니다 . 모든 작업은 전적으로 당신을 위해 완전히 이루어집니다. 당신은 할 필요가 ... 아무것도! :) 그냥이 예에서 두 줄의 코드를 붙여 넣습니다 .. stackoverflow.com/a/20988648/294884의 당신이 "단지 왼쪽을 선택 할 수 있습니다"권리 만 "또는"둘 다. "
— Fattie

답변:


212

세로 팬 제스처 인식기를 위해이 작업을 수행하면 나에게 효과적입니다.

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

그리고 스위프트의 경우 :

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

3
이것을 시도했지만, 번역은 종종 == (0,0)이므로 정확하지 않습니다
— zxcat

12
translationInView : 대신 velocityInView :를 사용하면 (0,0) 문제가 분명하지 않습니다.
— cbh2000

1
@ cbh2000 velocityInView대신에 사용하도록 답변을 업데이트했습니다 translationInView.
— Hejazi

19
@JoeBlow UISwipeGestureRecognizer는 스 와이프 제스처에 대한 응답으로 전환을 시작하는 쉬운 방법이지만 이산적인 제스처입니다. 제스처를 사용하여 전환을 애니메이션으로 만드는 것과 같은 지속적인 접근 방식을 찾고 있다면 UIPanGestureRecognizer를 사용하는 것이 좋습니다.
— Levi McCallum 2014

이것은 현명한 해결책입니다
— Jakub Truhlář

79

제공된 @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
            }
        }
    }
}

사용 예 :

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

4
이것은 절대적으로 가장 좋은 대답입니다. Apple이 이와 같은 기능을에 추가하지 않은 것은 너무 나쁩니다 UIPanGestureRecognizer.
— NRitH

사용 예를 제공 할 수 있습니까?
— user82395214

사랑 스럽습니다! 감사! 수평과 수직을 모두 쌓을 때 완벽하게 작동합니다 : 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);
— Han

오 이거 대단해! 감사!
— Baran Emre 2016 년

51

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

사용자가 선택한 비헤이비어에서 드래그를 시작하면 제스처가 트리거됩니다. 방향 속성을 올바른 값으로 설정하면 모든 설정이 완료됩니다.


나는 '재설정'이 처음에 불려지지 않는다고 생각합니다. initWithTarget:action:메소드를 추가하고 reset이라고 부르면 모두 잘되었습니다.
— colinta December

5
현재 구현에서는 DirectionPanGestureRecognizer설정하지 않은 경우 빠른 드래그를 무시하고 kDirectionPanThreshold = 20,이 경우 잘못된 알람이 발생할 수 있습니다. 나는 퍼팅 제안 abs(_moveX) > abs(_moveY)대신에 abs(_moveX) > kDirectionPanThreshold각각 수평 케이스를 변경.
— Dennis Krut

2
이것을 추가해야합니다. 이것은 나에게도 도움이되었지만, 팬 제스처 인식기를 트리거하기 위해 _drag = YES추가 self.state = UIGestureRecognizerStateChanged;
— 해야하는

13

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 도 내에서 가로 로만 스 와이프 하면이 팬 제스처가 트리거 될 수 있습니다.


2
좋은 대답입니다. 이것은 UIScrollView 제스처와 일반 제스처를 혼합 할 때 실제로 도움이되었습니다. 이 예제는 "enableThreshold"대신 "thresholdAngle"을 의미한다고 생각합니다. 그리고 NAN을 생성 할 수 있으므로 atan ()을 거의 사용하지 않아야합니다. 대신 atan2 ()를 사용하십시오.
— Brainware

9

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

이것은 실제로 팬이 기본보기 또는 tableView에서 진행되고 있는지 확인하는 것입니다.


3
두 뷰가 동일한 경우 비교하기 위해 -isEqual :을 호출하는 이유는 무엇입니까? 간단한 신원 확인으로 충분합니다. gestureRecognizer.view == self.view
— openfrog

6

게으른에 대한 리의 답변 스위프트 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
            }
        }
    }
}

4

나는했다 리 굿 리치 의 대답을 내가 특별히 하나의 방향으로 팬을 필요에 따라 확장. 다음과 같이 사용하십시오.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.
— AnBisw

당신은 "수입 UIKit.UIGestureRecognizerSubclass"사용할 필요가 @Annjawn
— shawnynicole

확인. 나는 그것을 몰랐다. import UIKit이 자동으로 가져올 것이라고 생각했습니다. 시도해 볼게요.
— AnBisw

2

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

실제로 이것은 좋은 해결책이 아니기 때문에 왼쪽에서 88 점 만 이동할 수 있습니다.
— 보 루트 토 마진

2

스위프트 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.


1

내가 해결 한 방법은 다음과 같습니다.

먼저 PanGesture Recognition을 동시에 활성화했습니다.

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

나는 여기에 예를 밀어 넣었다.

스크롤보기에서 사용자 정의 팬 추가


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

1

간단하게 사용할 수 있습니다 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];
    }
}

이 코드는 단순히 뷰를 패닝합니다. 방향 잠금이 구현되지 않았습니다.
— zakishaheen

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

1

신속한 방법

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
}

0

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
}




}

0

나는 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

            }

        }
    }
}

0

다른 모든 접근 방식은 UIGestureRecognizerDelegate서브 클래 싱을 기반으로하기 때문에 접근 방식을 공유하고 싶습니다 UIPanGestureRecognizer.

내 접근 방식은 런타임과 스위 즐링을 기반으로합니다. 이 방법에 대해 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)
    }
}

0

나는 이것을 시도했다 : 그것은 질문에 따라 나를 위해 일했다.

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

0

스위프트 4.2

나는 더 나아가 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
        }
    }
}

(사용 : https://github.com/fastred/SloppySwiper 및 https://stackoverflow.com/a/30607392/5790492 )


0

다음은 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)

-1

PanGestureRecognizer 인터페이스에는 다음 정의가 포함됩니다.

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

나는 이것을 확인하지 않았지만 아마도 서브 클래스를 통해 액세스 할 수 있습니다.


3
유망 해 보이지만 API는 노출되지 않습니다. 개인 API를 사용하면 일반적으로 Apple에서 거부하게됩니다.
— 윌리엄 데니스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.