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/SloppySwiperhttps://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.