UITapGestureRecognizer: farlo funzionare al tocco, non al ritocco?


84

Quello per cui sto usando l'evento tap è molto sensibile al tempo, quindi sono curioso di sapere se è possibile attivare UITapGestureRecognizer quando l'utente tocca semplicemente terra, piuttosto che richiedere anche loro di ritoccare?


1
Se aiuta, UITouch ha un metodo touchesStarted. Ma questo non sta usando i riconoscitori di gesti, come hai chiesto.
vqdave

Risposte:


141

Crea la tua sottoclasse TouchDownGestureRecognizer personalizzata e implementa i gesti nei tocchi

TouchDownGestureRecognizer.h

#import <UIKit/UIKit.h>

@interface TouchDownGestureRecognizer : UIGestureRecognizer

@end

TouchDownGestureRecognizer.m

#import "TouchDownGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>

@implementation TouchDownGestureRecognizer
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    if (self.state == UIGestureRecognizerStatePossible) {
        self.state = UIGestureRecognizerStateRecognized;
    }
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    self.state = UIGestureRecognizerStateFailed;
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    self.state = UIGestureRecognizerStateFailed;
}


@end

implementazione:

#import "TouchDownGestureRecognizer.h"
    TouchDownGestureRecognizer *touchDown = [[TouchDownGestureRecognizer alloc] initWithTarget:self action:@selector(handleTouchDown:)];
    [yourView addGestureRecognizer:touchDown];

-(void)handleTouchDown:(TouchDownGestureRecognizer *)touchDown{
    NSLog(@"Down");
}

Rapida implementazione:

import UIKit
import UIKit.UIGestureRecognizerSubclass

class TouchDownGestureRecognizer: UIGestureRecognizer
{
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        if self.state == .Possible
        {
            self.state = .Recognized
        }
    }

    override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        self.state = .Failed
    }

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent)
    {
        self.state = .Failed
    }
}

Ecco la sintassi Swift per il 2017 da incollare:

import UIKit.UIGestureRecognizerSubclass

class SingleTouchDownGestureRecognizer: UIGestureRecognizer {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            self.state = .recognized
        }
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        self.state = .failed
    }
}

Nota che questo è un sostituto immediato di UITap. Quindi in codice come ...

func add(tap v:UIView, _ action:Selector) {
    let t = UITapGestureRecognizer(target: self, action: action)
    v.addGestureRecognizer(t)
}

puoi tranquillamente passare a ....

func add(hairtriggerTap v:UIView, _ action:Selector) {
    let t = SingleTouchDownGestureRecognizer(target: self, action: action)
    v.addGestureRecognizer(t)
}

I test mostrano che non verrà chiamato più di una volta. Funziona come un sostituto immediato; puoi semplicemente scambiare tra le due chiamate.


10
+1 extra per l'importazione "UIGestureRecognizerSubclass.h". Bello.
Sebastian Hojas

3
Super non dovrebbe essere invocato nei metodi touchesBegin, touchesMoved, touchesEnded?
neoneye

3
@JasonSilberman hai dimenticato #import <UIKit / UIGestureRecognizerSubclass.h>
LE SANG

Come implementeresti il ​​doppio tocco con questo?
Babiker

1
@etayluz devi assegnare lo stato all'inizio e alla fine. Quindi controlla lo stato nell'handle. Il riconoscimento personalizzato significa che puoi controllare tutto.
LE SANG

184

Usa un UILongPressGestureRecognizer e impostalo minimumPressDurationsu 0. Agirà come un touch down durante ilUIGestureRecognizerStateBegan stato.

Per Swift 4+

func setupTap() {
    let touchDown = UILongPressGestureRecognizer(target:self, action: #selector(didTouchDown))
    touchDown.minimumPressDuration = 0
    view.addGestureRecognizer(touchDown)
}

@objc func didTouchDown(gesture: UILongPressGestureRecognizer) {
    if gesture.state == .began {
        doSomething()
    }
}

Per Objective-C

-(void)setupLongPress
{
   self.longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didLongPress:)];
   self.longPress.minimumPressDuration = 0;
   [self.view addGestureRecognizer:self.longPress];
}

-(void)didLongPress:(UILongPressGestureRecognizer *)gesture
{
   if (gesture.state == UIGestureRecognizerStateBegan){
      [self doSomething];
   }
}

4
per le persone a cui piace il generatore di interfacce (me), minimumPressDuration può anche essere impostato in IB (grazie Rob per l'ottima soluzione)
tmr

C'è un modo per rilevare il ritocco qui?
CalZone

1
@CalZone Sì, controlla lo stato dei gestiUIGestureRecognizerStateEnded
Rob Caraway

2
Una bella soluzione alternativa +1. minimumPressDurationpuò essere 0 tho.
Valentin Radu

1
PERICOLO spesso può essere richiamato più di una volta, con un tocco tipico.
Fattie

23

Swift (senza sottoclasse)

Ecco una versione Swift simile alla risposta Objective-C di Rob Caraway .

L'idea è di utilizzare un riconoscimento di gesti a pressione prolungata con l' minimumPressDurationimpostazione a zero piuttosto che utilizzare un riconoscimento di gesti di tocco. Questo perché il riconoscimento del gesto di pressione prolungata segnala gli eventi di inizio tocco mentre il gesto di tocco no.

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var myView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Add "long" press gesture recognizer
        let tap = UILongPressGestureRecognizer(target: self, action: #selector(tapHandler))
        tap.minimumPressDuration = 0
        myView.addGestureRecognizer(tap)
    }

    // called by gesture recognizer
    @objc func tapHandler(gesture: UITapGestureRecognizer) {

        // handle touch down and touch up events separately
        if gesture.state == .began {
            // do something...
            print("tap down")
        } else if gesture.state == .ended { // optional for touch up event catching
            // do something else...
            print("tap up")
        }
    }
}

1
@richy, anche a me è sembrato un po 'un trucco, dato che il nome è un riconoscimento di gesti di pressione lunga , ma questo metodo è molto più semplice della sottoclasse della vista e come hai detto, funziona benissimo.
Suragch

Questa soluzione causa problemi di scorrimento nelle sottoclassi
UIscrollView

Penso che l'approccio bater sia il riconoscimento di gesti personalizzati
SPatel

@ Suragch Mi sono imbattuto nello stesso problema con questo che ho incontrato con la risposta di Lessing. Questa risposta funziona ma ho perso la possibilità di scorrere perché non appena metto il dito sull'oggetto per farlo scorrere, pensa che sia un tocco. Come può differenziarli entrambi?
Lance Samaria

1
@LanceSamaria, se hai bisogno di un riconoscimento tattile più complesso del semplice tocco, utilizzerei un riconoscimento gestuale personalizzato.
Suragch

1

Questa è un'altra soluzione. Crea una sottoclasse di UIControl. Puoi usarlo come UIView anche in Storyboard perché UIControl è una sottoclasse di UIView.

class TouchHandlingView: UIControl {
}

E aggiungi un obiettivo:

@IBOutlet weak var mainView: TouchHandlingView!
...

mainView.addTarget(self, action: "startAction:", forControlEvents: .TouchDown)
...

Quindi l'azione designata verrà chiamata come UIButton:

func startAction(sender: AnyObject) {
    print("start")
}

1

Avevo bisogno della possibilità per la mia vista di avere un grilletto dei capelli, quindi non appena viene toccato risponde. L'utilizzo di entrambe le risposte @LESANG ha funzionato, così come la risposta di @RobCaraway . Il problema che ho riscontrato con entrambe le risposte è stato che ho perso la capacità di riconoscere i colpi. Avevo bisogno che la mia vista ruotasse durante lo scorrimento, ma non appena il mio dito ha toccato la vista è stato riconosciuto solo il tocco. Il tapRecognizer era troppo sensibile e non poteva distinguere tra un tocco e uno scorrimento.

Questo è quello che ho trovato in base alla risposta di @LESANG combinata con questa risposta e questa risposta .

Ho messo 6 commenti in ogni evento.

import UIKit.UIGestureRecognizerSubclass

class SingleTouchDownGestureRecognizer: UIGestureRecognizer {


    var wasSwiped = false

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {

        guard let view = self.view else { return }
        guard let touches = event.touches(for: view) else { return } // 1. compare that event in touchesBegan has touches for the view that is the same as the view to which your gesture recognizer was assigned

        if touches.first != nil {
            print("Finger touched!") // 2. this is when the user's finger first touches the view and is at locationA
            wasSwiped = false // 3. it would seem that I didn't have to set this to false because the property was already set to false but for some reason when I didn't add this it wasn't responding correctly. Basically set this to false
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {

        guard let touch = touches.first else { return }

        let newLocation = touch.location(in: self.view)
        let previousLocation = touch.previousLocation(in: self.view)

        if (newLocation.x > previousLocation.x) || (newLocation.x < previousLocation.x) {
            print("finger touch went right or left") // 4. when the user's finger first touches it's at locationA. If the the user moves their finger to either the left or the right then the finger is no longer at locationA. That means it moved which means a swipe occurred so set the "wasSwiped" property to true

            wasSwiped = true // 5. set the property to true because the user moved their finger
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        print("finger is no longer touching.") // 6. the user has lifted their finger off of the view. If "wasSwiped" is true then ".fail" but if it wasn't swiped then ".recognize"

        if wasSwiped {
            self.state = .failed
        } else {
            self.state = .recognized
        }
    }
}

E per usarlo in modo che la vista che lo utilizza ottenga la risposta del grilletto dei capelli e i gesti di scorrimento sinistro e destro .:

let tapGesture = SingleTouchDownGestureRecognizer(target: self, action: #selector(viewWasTapped(_:)))
myView.addGestureRecognizer(tapGesture)

let rightGesture = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeGesture(recognizer:)))
rightGesture.direction = .right
myView.addGestureRecognizer(rightGesture)

let leftGesture = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeGesture(recognizer:)))
leftGesture.direction = .left
myView.addGestureRecognizer(leftGesture)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.