UIPanGestureRecognizer - tylko w pionie lub w poziomie

146

Mam widok, który ma UIPanGestureRecognizerprzeciągnąć widok w pionie. Tak więc w wywołaniu zwrotnym aparatu rozpoznawania aktualizuję tylko współrzędną y, aby ją przenieść. Nadzór tego widoku ma opcję, UIPanGestureRecognizerktóra przeciąga widok poziomo, po prostu aktualizując współrzędną x.

Problem polega na tym, że pierwszy UIPanGestureRecognizerpolega na tym, że zdarzenie przesuwa widok w pionie, więc nie mogę użyć gestu superview.

próbowałem

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

i oba będą działać, ale tego nie chcę. Chcę, aby poziomo był wykrywany tylko wtedy, gdy ruch jest wyraźnie poziomy. Byłoby więc wspaniale, gdyby UIPanGestureRecognizermiał właściwość kierunku.

Jak mogę osiągnąć takie zachowanie? Uważam, że dokumentacja jest bardzo zagmatwana, więc może ktoś może to lepiej wyjaśnić tutaj.

LocoMike
źródło
Możesz odpowiedzieć na własne pytanie i zaakceptować odpowiedź, jeśli znalazłeś rozwiązanie.
jtbandes
@JoeBlow naprawdę? Więc może stworzyłeś kategorię gestów machnięcia, aby uzyskać tłumaczenie i prędkość gestu?
Roman Truba
2
Nie rozumiem, co mówisz. Jeśli chcesz wykryć przesunięcie w poziomie , jest to całkowicie i całkowicie wbudowane w system operacyjny . Cała praca jest całkowicie i całkowicie wykonana za Ciebie. Musisz ... nic! :) Po prostu wklej dwa wiersze kodu w tym przykładzie .. stackoverflow.com/a/20988648/294884 Zauważ, że możesz wybrać tylko po lewej stronie „tylko po prawej” lub „oba”.
Fattie

Odpowiedzi:

212

Po prostu zrób to dla rozpoznawania gestów przesuwania w pionie, działa dla mnie:

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

A dla Swift:

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIPanGestureRecognizer) -> Bool {
    let velocity = gestureRecognizer.velocity(in: someView)
    return abs(velocity.x) > abs(velocity.y)
}
Hejazi
źródło
3
próbowałem tego, ale tłumaczenie jest często == (0,0), więc nie jest dokładne
zxcat
12
Problem (0,0) nie jest widoczny, gdy velocityInView: jest używany zamiast translationInView :.
cbh2000
1
@ cbh2000 Zaktualizowałem odpowiedź, aby użyć velocityInViewzamiast translationInView.
Hejazi
19
@JoeBlow UISwipeGestureRecognizer to łatwy sposób na uruchomienie przejścia w odpowiedzi na gest machnięcia, ale jest to gest dyskretny. Jeśli ktoś szuka podejścia ciągłego - jak animowanie przejścia za pomocą gestu - najlepszym rozwiązaniem jest UIPanGestureRecognizer.
Levi McCallum,
To sprytne rozwiązanie
Jakub Truhlář
79

Stworzyłem rozwiązanie z podklasą, jak w odpowiedzi podanej przez @LocoMike, ale wykorzystałem bardziej skuteczny mechanizm wykrywania poprzez prędkość początkową, jaką zapewnia @Hejazi. Używam również języka Swift, ale w razie potrzeby powinno to być łatwe do przetłumaczenia na Obj-C.

Zalety w stosunku do innych rozwiązań:

  • Prostsze i bardziej zwięzłe niż inne rozwiązania do tworzenia podklas. Brak dodatkowego stanu do zarządzania.
  • Wykrywanie kierunku ma miejsce przed wysłaniem akcji Rozpoczęty, więc Twój selektor gestów przesuwania nie odbiera żadnych wiadomości, jeśli przeciągniesz w złym kierunku.
  • Po określeniu kierunku początkowego logika kierunku nie jest już konsultowana. Powoduje to ogólnie pożądane zachowanie, polegające na aktywowaniu aparatu rozpoznawania, jeśli początkowy kierunek jest prawidłowy, ale nie powoduje anulowania gestu po jego rozpoczęciu, jeśli palec użytkownika nie porusza się idealnie w tym kierunku.

Oto kod:

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

Przykład użycia:

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
źródło
4
To jest absolutnie najlepsza odpowiedź. Szkoda, że ​​Apple nie dodał takiej funkcjonalności do UIPanGestureRecognizer.
NRitH
Czy możesz podać przykład użycia?
user82395214
To jest urocze! Dzięki! Działa idealnie przy układaniu w poziomie i pionie: 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
Och, to jest niesamowite! Dzięki!
Baran Emre
51

Rozgryzłem to, tworząc podklasę 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

Spowoduje to wyzwolenie gestu tylko wtedy, gdy użytkownik zacznie przeciągać wybrane zachowanie. Ustaw właściwość direction na poprawną wartość i gotowe.

LocoMike
źródło
Myślę, że „reset” nie jest początkowo wywoływany. Dodano initWithTarget:action:metodę i wywołałem reset i wszystko było dobrze.
colinta
5
W obecnej implementacji DirectionPanGestureRecognizerzignoruje szybkie przeciąganie, chyba że ustawisz kDirectionPanThreshold = 20lub tak, w takim przypadku może to dać fałszywe alarmy. Proponuję odpowiednio wstawić abs(_moveX) > abs(_moveY)zamiast abs(_moveX) > kDirectionPanThresholdi zmienić wielkość liter poziomych.
Dennis Krut
2
Powinienem dodać, że to również było dla mnie pomocne, ale to, co musiałem dodać, aby wyzwalać rozpoznawanie gestów panoramy, znajdowało się w innej części if, pod linią, _drag = YESktórą dodałemself.state = UIGestureRecognizerStateChanged;
bolnad
13

Próbowałem ograniczyć prawidłowy obszar w poziomie za pomocą 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;
}

Następnie tylko przesunięcie w poziomie w zakresie ProguAngle stopień może wywołać ten gest przesunięcia .

Fabuła
źródło
2
Świetna odpowiedź. To naprawdę pomogło mi, gdy mieszałem gesty UIScrollView i zwykłe gesty. Myślę, że przykład miał na celu powiedzenie „resholdAngle ”zamiast„ enableThreshold ”. Rzadko powinieneś używać atan (), ponieważ może utworzyć NAN. Zamiast tego użyj atan2 ().
Brainware
9

Odpowiedź Swift 3.0: tylko uchwyty wykonuje gest pionowy

    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
źródło
6

Poniższe rozwiązanie rozwiązało mój problem:

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

W rzeczywistości jest to po prostu sprawdzenie, czy panoramowanie odbywa się w widoku głównym lub tableView.

Borut Tomazin
źródło
3
Po co wywoływać -isEqual: aby porównać, czy dwa widoki są takie same? Wystarczy zwykła kontrola tożsamości. gestRecognizer.view == self.view
openfrog
6

Szybka 3 wersja odpowiedzi Lee dla leniwych

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
źródło
4

Wziąłem Lee Goodrich „s odpowiedź i rozszerzyła je jako musiałem specjalnie jednym kierunku patelni. Użyj tego w ten sposób:let pan = PanDirectionGestureRecognizer(direction: .vertical(.up), target: self, action: #selector(handleCellPan(_:)))

Dodałem też kilka komentarzy, aby trochę jaśniej określić, jakie decyzje są faktycznie podejmowane.

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
źródło
Błąd w override func touchesMoved- Method does not override any method from its superclass.
AnBisw
@Annjawn Musisz użyć "import UIKit.UIGestureRecognizerSubclass"
shawnynicole Kwietnia
Dobrze. Nie byłem tego świadomy. Myślałem, że import UIKit automatycznie zaimportuje go. Dam temu szansę.
AnBisw
2

Można znaleźć kierunek przeciąganie UIViewprzez UIPanGestureRecognizer. Proszę postępować zgodnie z kodem.

 - (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
źródło
Właściwie to nie jest dobre rozwiązanie, ponieważ tylko te 88 punktów z lewej strony jest w stanie panoramować.
Borut Tomazin
2

Swift 4.2

Rozwiązaniem jest tylko obsługa gestów przesuwania w pionie, tak samo jak w poziomie.

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

Rozwiązanie 1 :

@objc func panAction(pan: UIPanGestureRecognizer) {

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

Rozwiązanie 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)
    }

Następnie gest machnięcia połknie gest przesunięcia. Oczywiście nie musisz nic robić w swipeAction.

William Hu
źródło
1

Oto jak rozwiązałem:

Najpierw włączyłem jednoczesne rozpoznawanie PanGesture.

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

return YES;

Następnie izoluję gesty przesunięcia w poziomie i w pionie (akumulator jest właściwością 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];

Podałem tutaj przykład:

dodaj niestandardową panoramę w widoku przewijania


źródło
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
źródło
1

Możesz użyć prostego panGestureRecognizer. Nie ma potrzeby używania pandirectionregognizerani rzeczy. Po prostu użyj wartości y translationInview poniższego kodu, przesuń widok przeciągania tylko w górę iw dół

- (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
źródło
Ten kod po prostu przesuwa widok. Brak blokady kierunku.
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];}

Stworzyłem metodę akcji PanGestureRecognizer @selector dla obiektu, który wymagał tylko przewijania w poziomie.

 UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(smileyDragged:)];
    [buttonObject addGestureRecognizer:gesture];
Ratz
źródło
1

Szybki sposób

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
źródło
0

Dla wszystkich użytkowników Swift, to wystarczy :)

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
źródło
0

Przyjąłem doskonałą odpowiedź Lee Goodricha i przeportowałem na 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

            }

        }
    }
}
przeciętny Joe
źródło
0

Chciałbym podzielić się moim podejściem, ponieważ wszystkie inne podejścia są oparte na klasach UIGestureRecognizerDelegatealbo na podklasach UIPanGestureRecognizer.

Moje podejście opiera się na środowisku wykonawczym i swizzlingu. Nie jestem w 100% pewien co do tego podejścia, ale możesz je samodzielnie przetestować i poprawić.

Ustaw kierunek dowolnego za UIPanGestureRecognizerpomocą tylko jednej linii kodu:

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

użyj pod 'UIPanGestureRecognizerDirection'lub kod:

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
źródło
0

Spróbowałem tego: co zadziałało dla mnie zgodnie z opisem pytania

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    if gestureRecognizer is UIPanGestureRecognizer {
        return true
    } else {
        return false
    }
}
Aadi007
źródło
0

SWIFT 4.2

Poszedłem dalej i wskazałem kierunek Pan Gest:

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

(Używany: https://github.com/fastred/SloppySwiper i https://stackoverflow.com/a/30607392/5790492 )

Nik Kov
źródło
0

Oto niestandardowy gest przesuwania w Swift 5

U może ograniczyć jego kierunek i maksymalny kąt w tym kierunku, możesz również ograniczyć jego minimalną prędkość w tym kierunku.

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

zadzwoń tak:

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

lub

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

PanGestureRecognizer interfejs zawiera następujące definicje:

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

Nie sprawdzałem tego, ale może jest to dostępne przez podklasę.

zxcat
źródło
3
wygląda obiecująco, ale ten interfejs API nie jest ujawniony. Korzystanie z prywatnych interfejsów API zazwyczaj skutkuje odrzuceniem przez Apple.
William Denniss,