UIPanGestureRecognizer - Seulement vertical ou horizontal

146

J'ai une vue qui permet de UIPanGestureRecognizerfaire glisser la vue verticalement. Donc, dans le rappel de reconnaissance, je ne mets à jour que la coordonnée y pour la déplacer. Le superview de cette vue a un UIPanGestureRecognizerqui fera glisser la vue horizontalement, en mettant simplement à jour la coordonnée x.

Le problème est que le premier UIPanGestureRecognizerest de prendre l'événement pour déplacer la vue verticalement, donc je ne peux pas utiliser le geste superview.

j'ai essayé

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

et les deux fonctionneront, mais je ne veux pas de ça. Je veux que l'horizontale ne soit détectée que si le mouvement est clairement horizontal. Ce serait donc génial si le UIPanGestureRecognizeravait une propriété de direction.

Comment puis-je obtenir ce comportement? Je trouve la documentation très déroutante, alors peut-être que quelqu'un pourra mieux l'expliquer ici.

LocoMike
la source
Il est normal de répondre à votre propre question et d'accepter la réponse, si vous avez trouvé la solution.
jtbandes
@JoeBlow vraiment? Alors, peut-être avez-vous créé une catégorie de geste de balayage pour recevoir la traduction et la vitesse du geste?
Roman Truba
2
Je ne comprends pas ce que vous dites. Si vous souhaitez détecter un balayage horizontal , c'est totalement et complètement intégré au système d'exploitation . Tout le travail est entièrement et totalement fait pour vous. Vous devez faire ... rien! :) Il suffit de coller dans les deux lignes de code dans cet exemple .. stackoverflow.com/a/20988648/294884 Notez que vous pouvez choisir la gauche que » « droit seulement » ou « les deux ».
Fattie

Réponses:

212

Faites simplement ceci pour le reconnaissance de mouvement de panoramique vertical, cela fonctionne pour moi:

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

Et pour Swift:

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIPanGestureRecognizer) -> Bool {
    let velocity = gestureRecognizer.velocity(in: someView)
    return abs(velocity.x) > abs(velocity.y)
}
Hejazi
la source
3
essayé, mais la traduction est souvent == (0,0), donc ce n'est pas précis
zxcat
12
Le problème (0,0) n'est pas apparent lorsque velocityInView: est utilisé à la place de translationInView :.
cbh2000
1
@ cbh2000 J'ai mis à jour la réponse pour utiliser à la velocityInViewplace de translationInView.
Hejazi
19
@JoeBlow Un UISwipeGestureRecognizer est un moyen facile de déclencher une transition en réponse à un geste de balayage, mais c'est un geste discret. Si quelqu'un cherche une approche continue - comme pour animer une transition avec un geste - un UIPanGestureRecognizer est la voie à suivre.
Levi McCallum
C'est une solution intelligente
Jakub Truhlář
79

J'ai créé une solution avec un sous-classement comme dans la réponse fournie par @LocoMike, mais j'ai utilisé le mécanisme de détection le plus efficace via la vitesse initiale fourni par @Hejazi. J'utilise également Swift, mais cela devrait être facile à traduire en Obj-C si vous le souhaitez.

Avantages par rapport aux autres solutions:

  • Plus simple et plus concis que les autres solutions de sous-classification. Aucun état supplémentaire à gérer.
  • La détection de la direction se produit avant l'envoi de l'action Commencée, de sorte que votre sélecteur de mouvement de panoramique ne reçoit aucun message si la mauvaise direction est balayée.
  • Une fois la direction initiale déterminée, la logique de direction n'est plus consultée. Cela se traduit par le comportement généralement souhaité d'activer votre reconnaissance si la direction initiale est correcte, mais n'annule pas le geste après qu'il a commencé si le doigt d'un utilisateur ne se déplace pas parfaitement dans la direction.

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

Exemple d'utilisation:

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
la source
4
C'est absolument la meilleure réponse. C'est dommage qu'Apple n'ait pas ajouté de fonctionnalités comme celle-ci au UIPanGestureRecognizer.
NRitH
Pouvez-vous donner un exemple d'utilisation?
user82395214
C'est adorable! Merci! Fonctionne parfaitement lors de l'empilement horizontal et vertical: 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 c'est génial! Merci!
Baran Emre
51

Je l'ai compris en créant une sous-classe de 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

Cela ne déclenchera le geste que si l'utilisateur commence à faire glisser le comportement sélectionné. Définissez la propriété direction sur une valeur correcte et vous êtes tous ensemble.

LocoMike
la source
Je pense que 'reset' n'est pas appelé au départ. Ajout d'une initWithTarget:action:méthode et réinitialisation appelée et tout allait bien.
colinta
5
Dans l'implémentation actuelle, DirectionPanGestureRecognizeril ne tiendra pas compte des traînées rapides, sauf si vous définissez kDirectionPanThreshold = 20ou ainsi, auquel cas il peut donner de fausses alarmes. Je suggère de mettre à la abs(_moveX) > abs(_moveY)place abs(_moveX) > kDirectionPanThresholdet de changer le cas horizontal respectivement.
Dennis Krut
2
Je devrais ajouter que cela m'a également été utile, mais ce que j'ai dû ajouter pour que le reconnaissance de mouvement de panoramique se déclenche était dans la partie autre du if, sous la ligne que _drag = YESj'ai ajoutéeself.state = UIGestureRecognizerStateChanged;
bolnad
13

J'ai essayé de contraindre la zone valide horizontalement avec 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;
}

Alors, seulement à l' intérieur de glisser thresholdAngle degré peut déclencher horizontalement ce geste pan.

Récit
la source
2
Très bonne réponse. Cela m'a vraiment aidé lorsque je mélangeais des gestes UIScrollView et des gestes réguliers. Je pense que l'exemple voulait dire "thresholdAngle" au lieu de "enableThreshold". Et vous devriez rarement utiliser atan () car il peut produire un NAN. Utilisez atan2 () à la place.
Brainware
9

Réponse Swift 3.0: il suffit de gérer le geste vertical

    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
la source
6

La solution suivante a résolu mon problème:

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

Il s'agit en fait simplement de vérifier si le panoramique est en cours sur la vue principale ou tableView.

Borut Tomazin
la source
3
Pourquoi appeler -isEqual: pour comparer si deux vues sont identiques? Un simple contrôle d'identité devrait suffire. gestureRecognizer.view == self.view
openfrog
6

Version Swift 3 de la réponse de Lee pour les paresseux

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
la source
4

J'ai pris la réponse de Lee Goodrich et l'ai étendue car j'avais besoin d'un panoramique à sens unique. Utilisez-le comme ceci:let pan = PanDirectionGestureRecognizer(direction: .vertical(.up), target: self, action: #selector(handleCellPan(_:)))

J'ai également ajouté quelques commentaires pour clarifier un peu les décisions qui sont réellement prises.

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
la source
Erreur dans override func touchesMoved- Method does not override any method from its superclass.
AnBisw
@Annjawn Vous devez utiliser "import UIKit.UIGestureRecognizerSubclass"
shawnynicole
D'accord. Je n'étais pas au courant de cela. Je pensais qu'import UIKit l'importerait automatiquement. Je vais essayer.
AnBisw
2

Vous pouvez trouver la direction qui UIViewpasse UIPanGestureRecognizer. Veuillez suivre le 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;
}
Arunjack
la source
En fait, ce n'est pas une bonne solution car seuls les 88 points de gauche sont capables de faire un panoramique.
Borut Tomazin
2

Swift 4.2

La solution est juste pour soutenir uniquement le geste panoramique verticalement, comme horizontal.

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

Solution 1 :

@objc func panAction(pan: UIPanGestureRecognizer) {

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

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

Ensuite, le geste de balayage avalera le geste de panoramique. Bien sûr, vous n'avez rien à faire swipeAction.

William Hu
la source
1

Voici comment j'ai résolu:

J'ai d'abord activé la reconnaissance simultanée PanGesture.

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

return YES;

Ensuite, j'isole les gestes de panoramique horizontal et vertical (l'accumulateur est la propriété 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];

J'ai poussé un exemple ici:

ajouter un panoramique personnalisé dans la vue par défilement


la source
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
la source
1

Vous pouvez utiliser simple panGestureRecognizer. Pas besoin d'utiliser pandirectionregognizerou de trucs. Utilisez simplement la valeur y du translationInview code ci - dessous déplacer la vue glisser uniquement vers le haut et le bas

- (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];
    }
}
Ajouter080bbA
la source
Ce code déplace simplement la vue. Aucun verrouillage directionnel n'est implémenté.
zakishaheen le
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];}

J'ai créé la méthode d'action PanGestureRecognizer @selector pour l'objet qui ne nécessitait que le défilement horizontal.

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

Voie rapide

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
la source
0

Pour tous les utilisateurs de Swift, cela fera l'affaire :)

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
la source
0

J'ai pris une excellente réponse de Lee Goodrich et porté sur 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

            }

        }
    }
}
Joe moyen
la source
0

J'adorerais partager mon approche car toutes les autres approches sont basées sur l'un UIGestureRecognizerDelegateou l' autre des sous-classements UIPanGestureRecognizer.

Mon approche est basée sur le runtime et le swizzling. Je ne suis pas sûr à 100% de cette approche, mais vous pouvez la tester et l'améliorer vous-même.

Définissez la direction de tout UIPanGestureRecognizeravec une seule ligne de code:

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

utilisez pod 'UIPanGestureRecognizerDirection'ou le 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)
    }
}
iWheelBuy
la source
0

J'ai essayé ceci: qui a fonctionné pour moi selon la question décrit

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

SWIFT 4.2

Je suis allé plus loin et fais une direction 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
        }
    }
}

(Utilisé: https://github.com/fastred/SloppySwiper et https://stackoverflow.com/a/30607392/5790492 )

Nik Kov
la source
0

Voici un geste de panoramique personnalisé dans Swift 5

U peut contraindre sa direction et l'angle maximum dans la direction, vous pouvez également contraindre sa vitesse minimum dans la direction.

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

appelle comme ça:

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

ou

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

PanGestureRecognizer interface contient les définitions suivantes:

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

Je n'ai pas vérifié cela, mais c'est peut-être accessible via une sous-classe.

zxcat
la source
3
semble prometteur, mais cette API n'est pas exposée. L'utilisation d'API privées entraîne généralement un rejet par Apple.
William Denniss