UIPanGestureRecognizer - Hanya vertikal atau horizontal

146

Saya memiliki tampilan yang memiliki UIPanGestureRecognizeruntuk menyeret tampilan secara vertikal. Jadi dalam callback pengenal, saya hanya memperbarui koordinat y untuk memindahkannya. Superview tampilan ini, memiliki UIPanGestureRecognizeryang akan menyeret tampilan secara horizontal, hanya memperbarui koordinat x.

Masalahnya adalah yang pertama UIPanGestureRecognizeradalah mengambil acara untuk memindahkan tampilan secara vertikal, jadi saya tidak bisa menggunakan gerakan superview.

saya telah mencoba

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

dan keduanya akan bekerja, tetapi saya tidak menginginkannya. Saya ingin secara horizontal terdeteksi hanya jika gerakannya jelas horisontal. Jadi akan lebih bagus jika UIPanGestureRecognizermemiliki properti arah.

Bagaimana saya bisa mencapai perilaku ini? Saya menemukan dokumen sangat membingungkan, jadi mungkin seseorang dapat menjelaskannya dengan lebih baik di sini.

LocoMike
sumber
Tidak apa-apa untuk menjawab pertanyaan Anda sendiri dan menerima jawabannya, jika Anda menemukan solusinya.
jtbandes
@ JoBlow sungguh? Jadi, mungkin Anda membuat kategori gerakan gesek untuk menerima terjemahan dan kecepatan gerakan?
Roman Truba
2
Saya tidak mengerti apa yang Anda katakan. Jika Anda ingin mendeteksi gesekan horizontal , ini sepenuhnya dan sepenuhnya bawaan untuk sistem operasi . Semua pekerjaan sepenuhnya dan sepenuhnya dilakukan untuk Anda. Anda perlu ... tidak melakukan apa pun! :) Cukup rekatkan pada dua baris kode dalam contoh ini .. stackoverflow.com/a/20988648/294884 Perhatikan bahwa Anda dapat memilih hanya "" hanya kanan "atau" keduanya ".
Fattie

Jawaban:

212

Lakukan saja ini untuk pengenal isyarat pan vertikal, ini berfungsi untuk saya:

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

Dan untuk Swift:

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIPanGestureRecognizer) -> Bool {
    let velocity = gestureRecognizer.velocity(in: someView)
    return abs(velocity.x) > abs(velocity.y)
}
Hejazi
sumber
3
mencoba ini, tetapi terjemahannya sering == (0,0), jadi tidak tepat
zxcat
12
Masalah (0,0) tidak jelas ketika velocityInView: digunakan alih-alih translationInView :.
cbh2000
1
@ cbh2000 Aku diperbaharui jawaban untuk menggunakan velocityInViewbukan translationInView.
Hejazi
19
@ JoBlow A UISwipeGestureRecognizer adalah cara mudah untuk melepaskan transisi dalam menanggapi gerakan gesek, tapi ini adalah gerakan terpisah. Jika seseorang mencari pendekatan berkelanjutan - untuk menghidupkan transisi dengan gerakan - sebuah UIPanGestureRecognizer adalah cara untuk pergi.
Levi McCallum
Ini adalah solusi cerdas
Jakub Truhlář
79

Saya menciptakan solusi dengan subclassing seperti pada jawaban yang disediakan @LocoMike, tetapi menggunakan mekanisme pendeteksian yang lebih efektif melalui kecepatan awal seperti yang disediakan oleh @Hejazi. Saya juga menggunakan Swift, tetapi ini harus mudah diterjemahkan ke Obj-C jika diinginkan.

Keuntungan dibandingkan solusi lain:

  • Lebih sederhana dan lebih ringkas daripada solusi subclass lainnya. Tidak ada status tambahan untuk dikelola.
  • Deteksi arah terjadi sebelum mengirim tindakan Mulai, jadi pemilih gerakan pan Anda tidak menerima pesan jika arah yang salah digesek.
  • Setelah arah awal ditentukan, logika arah tidak lagi dikonsultasikan. Ini menghasilkan perilaku yang umumnya diinginkan untuk mengaktifkan pengenal Anda jika arah awal benar, tetapi tidak membatalkan gerakan setelah dimulai jika jari pengguna tidak berjalan dengan sempurna di sepanjang arah.

Berikut kodenya:

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

Contoh penggunaan:

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:
    ...
}
Lee Goodrich
sumber
4
Ini benar-benar jawaban terbaik. Sayang sekali bahwa Apple belum menambahkan fungsionalitas seperti ini ke UIPanGestureRecognizer.
NRitH
Bisakah Anda memberikan contoh penggunaan?
user82395214
Ini indah! Terima kasih! Berfungsi sempurna saat menumpuk baik horisontal maupun vertikal: 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
Oh, ini luar biasa! Terima kasih!
Baran Emre
51

Saya menemukan cara membuat subclass dari 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

Ini hanya akan memicu gerakan jika pengguna mulai menyeret dalam perilaku yang dipilih. Tetapkan properti arah ke nilai yang benar dan Anda sudah siap.

LocoMike
sumber
Saya pikir 'reset' tidak dipanggil pada awalnya. Menambahkan initWithTarget:action:metode dan disebut reset dan semuanya baik-baik saja.
colinta
5
Dalam implementasi saat ini DirectionPanGestureRecognizerakan mengabaikan drag cepat, kecuali jika Anda mengatur kDirectionPanThreshold = 20atau lebih, dalam hal ini dapat memberikan alarm palsu. Saya menyarankan untuk meletakkan dan abs(_moveX) > abs(_moveY)bukannya abs(_moveX) > kDirectionPanThresholdmengganti kasus horizontal masing-masing.
Dennis Krut
2
Saya harus menambahkan ini ini juga bermanfaat bagi saya, tetapi apa yang harus saya tambahkan untuk memicu pengenal gerakan panci adalah di bagian lain dari if, di bawah baris yang _drag = YESsaya tambahkanself.state = UIGestureRecognizerStateChanged;
bolnad
13

Saya mencoba membatasi area yang valid secara horizontal dengan 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;
}

Kemudian, hanya menggesek dalam ambang batas derajat secara horizontal dapat memicu gerakan panci ini.

Cerita
sumber
2
Jawaban yang bagus Ini sangat membantu saya ketika saya mencampurkan gerakan UIScrollView dan gerakan biasa. Saya pikir contoh ini dimaksudkan untuk mengatakan "thresholdAngle" daripada "enableThreshold". Dan Anda sebaiknya jarang menggunakan atan () karena dapat menghasilkan NAN. Gunakan atan2 () sebagai gantinya.
Brainware
9

Swift 3.0 menjawab: hanya menangani gerakan vertikal

    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

}
Siavash Alp
sumber
6

Solusi berikut memecahkan masalah saya:

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

Ini sebenarnya hanya memeriksa apakah pan terjadi pada tampilan utama atau tableView.

Borut Tomazin
sumber
3
Mengapa memanggil -isEqual: untuk membandingkan jika dua tampilan sama? Pemeriksaan identitas sederhana harus cukup. gestureRecognizer.view == self.view
openfrog
6

Versi 3 jawaban Lee cepat untuk yang malas

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
            }
        }
    }
}
Cesar Varela
sumber
4

Aku mengambil Lee Goodrich 's jawaban dan diperpanjang seperti aku butuh khusus arah pan tunggal. Gunakan seperti ini:let pan = PanDirectionGestureRecognizer(direction: .vertical(.up), target: self, action: #selector(handleCellPan(_:)))

Saya juga menambahkan beberapa komentar untuk membuatnya sedikit lebih jelas keputusan apa yang sebenarnya dibuat.

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
                }
            }
        }
    }
}
Rob Booth
sumber
Kesalahan dalam override func touchesMoved- Method does not override any method from its superclass.
AnBisw
@Annjawn Anda harus menggunakan "import UIKit.UIGestureRecognizerSubclass"
shawnynicole
Baik. Saya tidak menyadarinya. Saya pikir mengimpor UIKit akan secara otomatis mengimpornya. Saya akan mencobanya.
AnBisw
2

Anda dapat menemukan arah yang diseret UIViewmelalui UIPanGestureRecognizer. Silakan ikuti kodenya.

 - (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;
}
Arunjack
sumber
Sebenarnya ini bukan solusi yang baik karena hanya 88 poin dari kiri yang bisa berjalan.
Borut Tomazin
2

Cepat 4.2

Solusinya adalah hanya untuk mendukung gerakan panci secara vertikal, sama seperti horizontal.

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

Solusi 1 :

@objc func panAction(pan: UIPanGestureRecognizer) {

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

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

Kemudian gerakan menggesek akan menelan gerakan panci. Tentu saja, Anda tidak perlu melakukan apa pun swipeAction.

William Hu
sumber
1

Inilah cara saya mengatasinya:

Pertama-tama saya mengaktifkan Pengenalan PanGesture secara bersamaan.

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

return YES;

Kemudian saya mengisolasi gerakan Pan Horisontal dan Vertikal (akumulator adalah properti 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];

Saya mendorong sebuah contoh di sini:

tambahkan custom pan di scrollview


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

Anda dapat menggunakan yang sederhana panGestureRecognizer. Tidak perlu menggunakan pandirectionregognizeratau barang-barang. Cukup gunakan nilai y dari Hanya translationInview tarik kode tarik tampilan di atas dan ke bawah

- (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];
    }
}
Add080bbA
sumber
Kode ini hanya menampilkan tampilan. Tidak ada kunci arah yang diterapkan.
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];}

Saya membuat metode tindakan PanGestureRecognizer @selector untuk objek yang hanya membutuhkan pengguliran horizontal.

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

Cara cepat

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
}
Adam Smaka
sumber
0

Untuk semua Anda pengguna Swift di luar sana, ini akan melakukan pekerjaan :)

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
}




}
Phil
sumber
0

Saya mengambil jawaban yang sangat baik oleh Lee Goodrich dan pindah ke 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

            }

        }
    }
}
rata-rata Joe
sumber
0

Saya ingin membagikan pendekatan saya karena semua pendekatan lain didasarkan pada salah satu UIGestureRecognizerDelegateatau subklasifikasi UIPanGestureRecognizer.

Pendekatan saya didasarkan pada runtime dan swizzling. Saya tidak 100% yakin tentang pendekatan ini, tetapi Anda dapat menguji dan memperbaikinya sendiri.

Tetapkan arah apa pun UIPanGestureRecognizerhanya dengan satu baris kode:

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

gunakan pod 'UIPanGestureRecognizerDirection'atau kode:

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)
    }
}
iWheelBuy
sumber
0

Saya mencoba ini: yang bekerja untuk saya sesuai dengan pertanyaan yang dijelaskan

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

SWIFT 4.2

Saya melangkah lebih jauh dan membuat arah 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
        }
    }
}

(Digunakan: https://github.com/fastred/SloppySwiper dan https://stackoverflow.com/a/30607392/5790492 )

Nik Kov
sumber
0

Ini adalah gerakan pan kustom di Swift 5

Anda dapat membatasi arah dan sudut maks dalam arah, Anda juga dapat membatasi kecepatan minimum dalam arah.

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

sebut seperti ini:

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

atau

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

PanGestureRecognizer antarmuka berisi definisi berikut:

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

Saya tidak memeriksa ini, tetapi mungkin dapat diakses melalui subkelas.

zxcat
sumber
3
terlihat menjanjikan, tetapi API itu tidak terbuka. Penggunaan API pribadi umumnya menghasilkan penolakan oleh Apple.
William Denniss