UIPanGestureRecognizer - Chỉ dọc hoặc ngang


146

Tôi có một chế độ xem có UIPanGestureRecognizerđể kéo chế độ xem theo chiều dọc. Vì vậy, trong cuộc gọi lại nhận dạng, tôi chỉ cập nhật tọa độ y để di chuyển nó. Giám sát của chế độ xem này, có chế độ UIPanGestureRecognizersẽ kéo chế độ xem theo chiều ngang, chỉ cần cập nhật tọa độ x.

Vấn đề là lần đầu tiên UIPanGestureRecognizerlà lấy sự kiện để di chuyển khung nhìn theo chiều dọc, vì vậy tôi không thể sử dụng cử chỉ giám sát.

Tôi đã thử

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

và cả hai sẽ hoạt động, nhưng tôi không muốn điều đó. Tôi muốn chỉ được phát hiện theo chiều ngang nếu chuyển động rõ ràng theo chiều ngang. Vì vậy, nó sẽ là tuyệt vời nếu UIPanGestureRecognizercó một tài sản hướng.

Làm thế nào tôi có thể đạt được hành vi này? Tôi thấy các tài liệu rất khó hiểu, vì vậy có lẽ ai đó có thể giải thích nó tốt hơn ở đây.


Bạn có thể trả lời câu hỏi của mình và chấp nhận câu trả lời, nếu bạn tìm ra giải pháp.
jtbandes

@JoeBlow thật sao? Vì vậy, có lẽ bạn đã thực hiện thể loại cử chỉ vuốt để nhận bản dịch và vận tốc của cử chỉ?
Roman Truba

2
Tôi không hiểu bạn đang nói gì. Nếu bạn muốn phát hiện thao tác vuốt ngang , thì điều này được tích hợp hoàn toàn và hoàn toàn cho hệ điều hành . Tất cả các công việc là hoàn toàn và hoàn thành cho bạn. Bạn cần làm ... không có gì! :) Chỉ cần dán vào hai dòng mã trong ví dụ này .. stackoverflow.com/a/20988648/294884 Lưu ý rằng bạn chỉ có thể chọn trái "" chỉ bên phải "hoặc" cả hai ".
Fattie

Câu trả lời:


212

Chỉ cần làm điều này cho nhận dạng cử chỉ pan dọc, nó hoạt động với tôi:

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

Và đối với Swift:

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

3
đã thử cách này, nhưng bản dịch thường là == (0,0), vì vậy nó không chính xác
zxcat

12
Vấn đề (0,0) không rõ ràng khi velocityInView: được sử dụng thay cho dịchInView :.
cbh2000

1
@ cbh2000 Tôi đã cập nhật câu trả lời để sử dụng velocityInViewthay vì translationInView.
Hejazi

19
@JoeBlow Một UISwipeGestureRecognizer là một cách dễ dàng để thực hiện chuyển đổi để đáp ứng với cử chỉ vuốt, nhưng đó là một cử chỉ riêng biệt. Nếu ai đó đang tìm kiếm một cách tiếp cận liên tục, thì để tạo hiệu ứng chuyển đổi với một cử chỉ, thì một UIPanGestureRecognizer là cách để đi.
Levi McCallum

Đây là giải pháp thông minh
Jakub Truhlář

79

Tôi đã tạo ra một giải pháp với phân lớp như trong câu trả lời @LocoMike cung cấp, nhưng đã sử dụng cơ chế phát hiện hiệu quả hơn thông qua vận tốc ban đầu được cung cấp bởi @Hejazi. Tôi cũng đang sử dụng Swift, nhưng điều này sẽ dễ dàng dịch sang Obj-C nếu muốn.

Ưu điểm so với các giải pháp khác:

  • Đơn giản và ngắn gọn hơn các giải pháp phân lớp khác. Không có nhà nước bổ sung để quản lý.
  • Phát hiện hướng xảy ra trước khi gửi hành động Bắt đầu, do đó, bộ chọn cử chỉ pan của bạn không nhận được tin nhắn nếu chuyển hướng sai.
  • Sau khi xác định hướng ban đầu, logic hướng không còn được tham khảo nữa. Điều này dẫn đến hành vi mong muốn chung là kích hoạt bộ nhận dạng của bạn nếu hướng ban đầu là chính xác, nhưng không hủy bỏ cử chỉ sau khi nó bắt đầu nếu ngón tay của người dùng không di chuyển hoàn hảo theo hướng.

Đây là mã:

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

Ví dụ về cách sử dụng:

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
Đây hoàn toàn là câu trả lời tốt nhất. Thật tệ khi Apple không thêm chức năng như thế này vào UIPanGestureRecognizer.
NRitH

Bạn có thể cung cấp một ví dụ sử dụng?
dùng82395214

Cái này thật đáng yêu! Cảm ơn! Hoạt động hoàn hảo khi xếp chồng cả ngang và dọc: 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

Ồ điều này thật tuyệt vời! Cảm ơn!
Baran Emre

51

Tôi đã tìm ra nó tạo ra một lớp con của 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

Điều này sẽ chỉ kích hoạt cử chỉ nếu người dùng bắt đầu kéo trong hành vi đã chọn. Đặt thuộc tính hướng thành một giá trị chính xác và bạn đã thiết lập xong.


Tôi nghĩ rằng 'thiết lập lại' không được gọi ban đầu. Đã thêm một initWithTarget:action:phương thức và được gọi là thiết lập lại và tất cả đều tốt.
colinta

5
Trong triển khai hiện tại DirectionPanGestureRecognizersẽ bỏ qua các thao tác kéo nhanh, trừ khi bạn đặt kDirectionPanThreshold = 20hoặc lâu hơn, trong trường hợp đó, nó có thể đưa ra cảnh báo sai. Tôi đề nghị đặt abs(_moveX) > abs(_moveY)thay vì abs(_moveX) > kDirectionPanThresholdvà thay đổi trường hợp ngang tương ứng.
Dennis Krut

2
Tôi cũng nên thêm cái này cũng hữu ích cho tôi, nhưng cái tôi phải thêm vào để có bộ nhận dạng cử chỉ pan để kích hoạt là ở phần khác của if, dưới dòng _drag = YEStôi đã thêmself.state = UIGestureRecognizerStateChanged;
bolnad 28/03/2016

13

Tôi đã cố gắng hạn chế khu vực hợp lệ theo chiều ngang với 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;
}

Sau đó, chỉ cần vuốt trong ngưỡng Độ ngang theo chiều ngang mới có thể kích hoạt cử chỉ xoay này.


2
Câu trả lời chính xác. Điều này thực sự giúp ích cho tôi khi tôi trộn các cử chỉ UIScrollView và các cử chỉ thông thường. Tôi nghĩ ví dụ này có nghĩa là nói "ngưỡng" thay vì "enableThr Ngưỡng". Và bạn hiếm khi sử dụng atan () vì nó có thể tạo ra NAN. Sử dụng atan2 () thay thế.
Brainware

9

Trả lời Swift 3.0: chỉ cần xử lý cử chỉ dọc

    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

Giải pháp sau đây đã giải quyết vấn đề của tôi:

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

Đây thực sự chỉ là kiểm tra xem pan đang ở chế độ xem chính hay bảng xem.


3
Tại sao gọi -isEqual: để so sánh nếu hai chế độ xem giống nhau? Một kiểm tra danh tính đơn giản nên đủ. cử chỉ nhận thức.view == self.view
openfrog

6

Phiên bản Swift 3 của Lee dành cho người lười biếng

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

Tôi lấy câu trả lời của Lee Goodrich và mở rộng nó khi tôi cần một chiếc chảo định hướng duy nhất. Sử dụng nó như thế này:let pan = PanDirectionGestureRecognizer(direction: .vertical(.up), target: self, action: #selector(handleCellPan(_:)))

Tôi cũng đã thêm một số bình luận để làm cho nó rõ ràng hơn một chút về những quyết định đang thực sự được đưa ra.

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

Lỗi trong override func touchesMoved- Method does not override any method from its superclass.
AnBisw

@Annjawn Bạn phải sử dụng "nhập UIKit.UIGestureRecognizerSub class"
Shawnynicole

Đồng ý. Tôi đã không nhận thức được điều đó. Tôi nghĩ nhập UIKit sẽ tự động nhập nó. Tôi sẽ thử.
AnBisw

2

Bạn có thể tìm thấy hướng kéo UIViewqua UIPanGestureRecognizer. Hãy làm theo mã.

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

Trên thực tế đây không phải là giải pháp tốt vì chỉ 88 điểm từ trái mới có thể xoay được.
Borut Tomazin

2

Swift 4.2

Giải pháp chỉ dành cho hỗ trợ cử chỉ pan theo chiều dọc, giống như ngang.

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

Giải pháp 1 :

@objc func panAction(pan: UIPanGestureRecognizer) {

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

Giải pháp 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)
    }

Sau đó cử chỉ vuốt sẽ nuốt cử chỉ pan. Tất nhiên, bạn không cần phải làm gì cả swipeAction.


1

Đây là cách tôi giải quyết:

Đầu tiên tôi kích hoạt Đồng thời PanGesture Recognition.

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

return YES;

Sau đó, tôi cách ly cử chỉ Pan ngang và dọc (tích lũy là thuộc tính 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];

Tôi đã đẩy một ví dụ ở đây:

thêm pan tùy chỉnh trong scrollview


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

Bạn có thể sử dụng đơn giản panGestureRecognizer. Không cần sử dụng pandirectionregognizerhoặc công cụ. Chỉ cần sử dụng giá trị y của translationInview Mã bên dưới di chuyển kéo chỉ xem lên xuống

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

Mã này chỉ đơn giản là mở ra xem. Không có khóa định hướng được thực hiện.
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];}

Tôi đã tạo phương thức hành động PanselestureRecognizer @selector cho đối tượng chỉ cần cuộn ngang.

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

1

Cách nhanh chóng

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

Đối với tất cả những người dùng Swift của bạn ngoài kia, điều này sẽ thực hiện công việc :)

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

Tôi đã nhận được một câu trả lời xuất sắc của Lee Goodrich và chuyển sang 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

Tôi rất thích chia sẻ cách tiếp cận của mình vì tất cả các cách tiếp cận khác đều dựa trên một trong hai UIGestureRecognizerDelegatehoặc phân lớp UIPanGestureRecognizer.

Cách tiếp cận của tôi dựa trên thời gian chạy và swizzling. Tôi không chắc chắn 100% về phương pháp này, nhưng bạn có thể tự kiểm tra và cải thiện nó.

Đặt hướng của bất kỳ UIPanGestureRecognizerchỉ với một dòng mã:

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

sử dụng pod 'UIPanGestureRecognizerDirection'hoặc mã:

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

Tôi đã thử điều này: nó hoạt động với tôi theo câu hỏi mô tả

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

0

Chuyển đổi 4.2

Tôi đã đi xa hơn và thực hiện một hướng 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
        }
    }
}

(Được sử dụng: https://github.com/fastred/SloppySwiperhttps://stackoverflow.com/a/30607392/5790492 )


0

Đây là một cử chỉ pan tùy chỉnh trong Swift 5

Bạn có thể hạn chế hướng của nó và góc tối đa theo hướng, bạn cũng có thể hạn chế tốc độ tối thiểu của nó theo hướng.

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

gọi như thế này:

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

hoặc là

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

-1

PanGestureRecognizer giao diện chứa các định nghĩa sau:

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

Tôi đã không kiểm tra điều này, nhưng có lẽ nó có thể truy cập thông qua lớp con.


3
có vẻ đầy hứa hẹn, nhưng API đó không bị lộ. Việc sử dụng API riêng thường dẫn đến sự từ chối của Apple.
William Denniss
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.