UITapGestureRecognizer: ¿hacer que funcione al aterrizar, no al retocar?

84

Para lo que estoy usando el evento de tap es muy urgente, así que tengo curiosidad por saber si es posible hacer que UITapGestureRecognizer se active cuando el usuario simplemente aterriza, en lugar de pedirle que también se retoque.

user212541
fuente
1
Si ayuda, UITouch tiene un método touchesStarted. Pero eso no es usar reconocedores de gestos, como preguntaste.
vqdave

Respuestas:

139

Cree su subclase TouchDownGestureRecognizer personalizada e implemente gestos en los toques

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

implementación:

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

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

Implementación rápida:

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

Aquí está la sintaxis de Swift para 2017 para pegar:

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

Tenga en cuenta que este es un reemplazo directo de UITap. Entonces, en código como ...

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

puede cambiar de forma segura a ....

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

Las pruebas muestran que no se llamará más de una vez. Funciona como un reemplazo directo; simplemente puede cambiar entre las dos llamadas.

LE SANG
fuente
10
+1 adicional para la importación "UIGestureRecognizerSubclass.h". Agradable.
Sebastian Hojas
3
¿No debería invocarse super en los métodos touchesBegin, touchesMoved, touchesEnded?
neoneye
3
@JasonSilberman olvidaste #import <UIKit / UIGestureRecognizerSubclass.h>
LE SANG
¿Cómo implementaría doble toque con esto?
Babiker
1
@etayluz has asignado un estado para comenzar y terminar. Luego verifique su estado en el mango. El reconocedor personalizado significa que puedes controlar todo.
LE SANG
184

Use un UILongPressGestureRecognizer y configúrelo minimumPressDurationen 0. Actuará como un touchdown durante el UIGestureRecognizerStateBeganestado.

Para 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()
    }
}

Para 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];
   }
}
Rob Caraway
fuente
4
para las personas a las que les gusta el constructor de interfaces (yo), minimumPressDuration también se puede configurar en IB (gracias Rob por la gran solución)
tmr
¿Hay alguna forma de detectar los retoques aquí?
CalZone
1
@CalZone Sí, verifique el estado de los gestosUIGestureRecognizerStateEnded
Rob Caraway
2
Una buena solución +1. minimumPressDurationpuede ser 0 tho.
Valentin Radu
1
PELIGRO A menudo se puede llamar más de una vez, con un toque típico.
Fattie
23

Swift (sin subclases)

Aquí hay una versión Swift similar a la respuesta Objective-C de Rob Caraway .

La idea es utilizar un reconocedor de gestos de pulsación larga con el minimumPressDurationajuste en cero en lugar de utilizar un reconocedor de gestos de pulsación. Esto se debe a que el reconocedor de gestos de pulsación prolongada informa los eventos de inicio táctil, mientras que el gesto de pulsación no lo hace.

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")
        }
    }
}
Suragch
fuente
1
@richy, también me pareció un truco, ya que el nombre es reconocedor de gestos de pulsación larga , pero este método es mucho más fácil que subclasificar la vista y, como dijiste, funciona muy bien.
Suragch
Esta solución causa un problema de desplazamiento en las subclases
UIscrollView
Creo que el enfoque de bater es el reconocedor de gestos personalizado
SPatel
@Suragch Me encontré con el mismo problema con esto que encontré con la respuesta de Lessing. Esta respuesta funciona, pero perdí la capacidad de deslizar porque tan pronto como coloco mi dedo en el objeto para deslizarlo, cree que es un toque. ¿Cómo se puede diferenciar entre ambos?
Lance Samaria
1
@LanceSamaria, si necesita un reconocimiento táctil más complejo que solo tocar, usaría un reconocedor de gestos personalizado.
Suragch
1

Ésta es otra solución. Cree una subclase de UIControl. Puede usarlo como UIView incluso en Storyboard porque UIControl es una subclase de UIView.

class TouchHandlingView: UIControl {
}

Y agregaTarget a él:

@IBOutlet weak var mainView: TouchHandlingView!
...

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

Entonces la acción designada se llamará como UIButton:

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

Necesitaba la capacidad de mi vista para tener un gatillo de pelo, de modo que tan pronto como se toca, responde. El uso de la respuesta @LESANG funcionó y también lo hizo el uso de la respuesta @RobCaraway . El problema que encontré con ambas respuestas fue que perdí la capacidad de reconocer golpes. Necesitaba que mi vista girara cuando se deslizaba, pero tan pronto como mi dedo tocó la vista, solo se reconoció el grifo. TapRecognizer era demasiado sensible y no podía diferenciar entre un toque y un deslizamiento.

Esto es lo que se me ocurrió en función de la respuesta de @LESANG combinada con esta respuesta y esta respuesta .

Pongo 6 comentarios en cada 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
        }
    }
}

Y usarlo para que la vista que lo usa obtenga la respuesta del gatillo del cabello y los gestos de deslizamiento hacia la izquierda y hacia la derecha:

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)
Lance Samaria
fuente