UIPanGestureRecognizer - Nur vertikal oder horizontal


146

Ich habe eine Ansicht, die UIPanGestureRecognizerdie Ansicht vertikal ziehen muss. Im Erkennungsrückruf aktualisiere ich also nur die y-Koordinate, um sie zu verschieben. Die Übersicht dieser Ansicht hat eine UIPanGestureRecognizer, die die Ansicht horizontal zieht und nur die x-Koordinate aktualisiert.

Das Problem ist, dass der erste UIPanGestureRecognizerdas Ereignis verwendet, um die Ansicht vertikal zu verschieben, sodass ich die Übersichtsgeste nicht verwenden kann.

Ich habe versucht

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

und beide werden funktionieren, aber das will ich nicht. Ich möchte, dass die Horizontale nur erkannt wird, wenn die Bewegung eindeutig horizontal ist. Es wäre also toll, wenn die UIPanGestureRecognizereine Richtungseigenschaft hätten.

Wie kann ich dieses Verhalten erreichen? Ich finde die Dokumente sehr verwirrend, deshalb kann es hier vielleicht jemand besser erklären.


Es ist in Ordnung, Ihre eigene Frage zu beantworten und die Antwort zu akzeptieren, wenn Sie die Lösung gefunden haben.
Jtbandes

@ JoeBlow wirklich? Vielleicht haben Sie eine Kategorie von Wischgesten erstellt, um die Übersetzung und die Geschwindigkeit der Gesten zu erhalten?
Roman Truba

2
Ich verstehe nicht, was du sagst. Wenn Sie einen horizontalen Wisch erkennen möchten , ist dieser vollständig und vollständig in das Betriebssystem integriert . Die ganze Arbeit ist ganz und gar für Sie erledigt. Du musst ... nichts tun! :) Fügen Sie einfach die beiden Codezeilen in diesem Beispiel ein. Stackoverflow.com/a/20988648/294884 Beachten Sie, dass Sie nur links "" nur rechts "oder" beide "auswählen können.
Fattie

Antworten:


212

Tun Sie dies einfach für den vertikalen Pan-Gesten-Erkenner, es funktioniert für mich:

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

Und für Swift:

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

3
habe dies versucht, aber die Übersetzung ist oft == (0,0), daher ist es nicht präzise
zxcat

12
Das (0,0) -Problem ist nicht ersichtlich, wenn VelocityInView: anstelle von translationInView: verwendet wird.
cbh2000

1
@ cbh2000 Ich habe die Antwort aktualisiert, um sie velocityInViewanstelle von zu verwenden translationInView.
Hejazi

19
@JoeBlow Ein UISwipeGestureRecognizer ist eine einfache Möglichkeit, einen Übergang als Reaktion auf eine Wischgeste auszulösen, aber es ist eine diskrete Geste. Wenn jemand nach einem kontinuierlichen Ansatz sucht, um einen Übergang mit einer Geste zu animieren, ist ein UIPanGestureRecognizer der richtige Weg.
Levi McCallum

Dies ist eine clevere Lösung
Jakub Truhlář

79

Ich habe eine Lösung mit Unterklassen wie in der Antwort @LocoMike erstellt, aber den effektiveren Erkennungsmechanismus über die Anfangsgeschwindigkeit verwendet, wie er von @Hejazi bereitgestellt wird. Ich verwende auch Swift, aber dies sollte auf Wunsch leicht in Obj-C zu übersetzen sein.

Vorteile gegenüber anderen Lösungen:

  • Einfacher und prägnanter als andere Lösungen für Unterklassen. Kein zusätzlicher Status zu verwalten.
  • Die Richtungserkennung erfolgt vor dem Senden der Aktion "Begann", sodass Ihre Schwenkgestenauswahl keine Nachrichten empfängt, wenn die falsche Richtung gewischt wird.
  • Nachdem die Anfangsrichtung bestimmt wurde, wird die Richtungslogik nicht mehr konsultiert. Dies führt zu dem allgemein gewünschten Verhalten beim Aktivieren Ihres Erkenners, wenn die ursprüngliche Richtung korrekt ist, bricht die Geste jedoch nicht ab, nachdem sie begonnen hat, wenn sich der Finger eines Benutzers nicht perfekt entlang der Richtung bewegt.

Hier ist der Code:

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

Anwendungsbeispiel:

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
Dies ist absolut die beste Antwort. Es ist schade, dass Apple dem nicht solche Funktionen hinzugefügt hat UIPanGestureRecognizer.
NRitH

Können Sie ein Anwendungsbeispiel angeben?
user82395214

Das ist schön! Vielen Dank! Funktioniert perfekt beim Stapeln sowohl horizontal als auch 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, das ist großartig! Vielen Dank!
Baran Emre

51

Ich habe herausgefunden, dass eine Unterklasse von UIPanGestureRecognizer erstellt wurde

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

Dies löst die Geste nur aus, wenn der Benutzer das ausgewählte Verhalten einzieht. Setzen Sie die Richtungseigenschaft auf einen korrekten Wert und Sie sind fertig.


Ich denke, 'Reset' wird anfangs nicht aufgerufen. Fügte eine initWithTarget:action:Methode hinzu und rief reset auf und alles war gut.
Colinta

5
In der aktuellen Implementierung DirectionPanGestureRecognizerwerden schnelle Drags ignoriert, es sei denn, Sie setzen kDirectionPanThreshold = 20oder so. In diesem Fall kann es zu Fehlalarmen kommen. Ich schlage vor, abs(_moveX) > abs(_moveY)statt abs(_moveX) > kDirectionPanThresholdhorizontaler Groß- und Kleinschreibung zu setzen.
Dennis Krut

2
Ich sollte hinzufügen, dass dies auch für mich hilfreich war, aber was ich hinzufügen musste, um den Pan-Gesten- _drag = YESself.state = UIGestureRecognizerStateChanged;
Erkenner

13

Ich habe versucht, den gültigen Bereich mit UIPanGestureRecognizer horizontal einzuschränken.

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

Nur horizontales Wischen innerhalb des Schwellenwerts kann diese Schwenkgeste auslösen.


2
Gute Antwort. Dies hat mir sehr geholfen, als ich UIScrollView-Gesten und reguläre Gesten gemischt habe. Ich denke, das Beispiel sollte "ThresholdAngle" anstelle von "enableThreshold" sagen. Und Sie sollten atan () selten verwenden, da es ein NAN erzeugen kann. Verwenden Sie stattdessen atan2 ().
Brainware

9

Schnelle 3.0-Antwort: Nur Griffe machen die vertikale Geste

    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

Die folgende Lösung hat mein Problem gelöst:

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

Dies ist eigentlich nur zu überprüfen, ob Pan in der Hauptansicht oder in der Tabellenansicht ist.


3
Warum -isEqual aufrufen: um zu vergleichen, ob zwei Ansichten gleich sind? Eine einfache Identitätsprüfung sollte ausreichen. gestureRecognizer.view == self.view
openfrog

6

Schnelle 3-Version von Lees Antwort für die Faulen

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

Ich nahm Lee Goodrich ‚s Antwort und erweitert es , wie ich speziell eine einzige Richtung pan benötigt. Verwenden Sie es so:let pan = PanDirectionGestureRecognizer(direction: .vertical(.up), target: self, action: #selector(handleCellPan(_:)))

Ich habe auch einige Kommentare hinzugefügt, um ein wenig klarer zu machen, welche Entscheidungen tatsächlich getroffen werden.

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

Fehler in override func touchesMoved- Method does not override any method from its superclass.
AnBisw

@Annjawn Sie müssen "import UIKit.UIGestureRecognizerSubclass"
shawnynicole

OK. Das war mir nicht bewusst. Ich dachte, UIKit importieren würde es automatisch importieren. Ich werde es versuchen.
AnBisw

2

Sie können die Richtung finden, die sich UIViewdurchzieht UIPanGestureRecognizer. Bitte folgen Sie dem Code.

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

Eigentlich ist dies keine gute Lösung, da nur die 88 Punkte von links schwenken können.
Borut Tomazin

2

Swift 4.2

Die Lösung besteht nur darin, die Schwenkgeste nur vertikal wie horizontal zu unterstützen.

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

Lösung 1 :

@objc func panAction(pan: UIPanGestureRecognizer) {

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

Lösung 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)
    }

Dann verschluckt die Wischgeste die Schwenkgeste. Natürlich müssen Sie nichts tun swipeAction.


1

So habe ich beschlossen:

Zuerst habe ich die gleichzeitige PanGesture-Erkennung aktiviert.

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

return YES;

Dann isoliere ich horizontale und vertikale Schwenkgesten (der Akkumulator ist die Eigenschaft 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];

Ich habe hier ein Beispiel angeführt:

Hinzufügen eines benutzerdefinierten Schwenks in der Bildlaufansicht


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

Sie können einfach verwenden panGestureRecognizer. Keine Notwendigkeit zu benutzen pandirectionregognizeroder so. Verwenden Sie einfach den y-Wert von translationInview Unter Code verschieben Sie die Ansicht nur nach oben und unten

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

Dieser Code schwenkt einfach die Ansicht. Es ist keine Richtungssperre implementiert.
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];}

Ich habe die Aktionsmethode PanGestureRecognizer @selector für das Objekt erstellt, für das nur horizontales Scrollen erforderlich ist.

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

1

Schneller Weg

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

Für alle Swift-Benutzer da draußen wird dies den Job machen :)

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

Ich habe eine ausgezeichnete Antwort von Lee Goodrich erhalten und auf Swift 3 portiert

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

Ich würde gerne meinen Ansatz teilen, da alle anderen Ansätze entweder auf UIGestureRecognizerDelegateoder auf Unterklassen basieren UIPanGestureRecognizer.

Mein Ansatz basiert auf Laufzeit und Swizzling. Ich bin mir bei diesem Ansatz nicht 100% sicher, aber Sie können ihn selbst testen und verbessern.

Stellen Sie die Richtung von jedem UIPanGestureRecognizermit nur einer Codezeile ein:

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

verwenden pod 'UIPanGestureRecognizerDirection'oder den Code:

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

Ich habe das versucht: was bei mir funktioniert hat wie in der Frage beschrieben

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

0

SWIFT 4.2

Ich ging weiter und machte eine Richtung 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
        }
    }
}

(Verwendet: https://github.com/fastred/SloppySwiper und https://stackoverflow.com/a/30607392/5790492 )


0

Hier ist eine benutzerdefinierte Schwenkgeste in Swift 5

U kann seine Richtung und den maximalen Winkel in der Richtung einschränken, Sie können auch seine minimale Geschwindigkeit in der Richtung einschränken.

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

so anrufen:

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

oder

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

-1

PanGestureRecognizer Schnittstelle enthält die folgenden Definitionen:

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

Ich habe das nicht überprüft, aber vielleicht ist es über die Unterklasse zugänglich.


3
sieht vielversprechend aus, aber diese API ist nicht verfügbar. Die Verwendung privater APIs führt im Allgemeinen zur Ablehnung durch Apple.
William Denniss
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.