UITapGestureRecognizer - funktioniert es beim Aufsetzen, nicht beim Aufsetzen?

81

Wofür ich das Tap-Ereignis verwende, ist sehr zeitkritisch. Daher bin ich gespannt, ob es möglich ist, UITapGestureRecognizer zu aktivieren, wenn der Benutzer einfach aufsetzt, anstatt dass er auch nachbessern muss.

user212541
quelle
1
Wenn es hilft, verfügt UITouch über eine touchStarted-Methode. Aber das verwendet keine Gestenerkenner, wie Sie gefragt haben.
vqdave

Antworten:

138

Erstellen Sie Ihre benutzerdefinierte TouchDownGestureRecognizer-Unterklasse und implementieren Sie die Geste in touchBegan:

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

Implementierung:

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

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

Schnelle Implementierung:

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

Hier ist die Swift-Syntax für 2017 zum Einfügen:

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

Beachten Sie, dass dies ein Ersatz für ist UITap. Also in Code wie ...

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

Sie können sicher wechseln zu ....

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

Tests zeigen, dass es nicht mehr als einmal aufgerufen wird. Es funktioniert als Ersatz. Sie können einfach zwischen den beiden Anrufen wechseln.

LE SANG
quelle
10
extra +1 für den Import "UIGestureRecognizerSubclass.h". Nett.
Sebastian Hojas
3
Sollte Super nicht in den Methoden touchBegin, touchMoved, touchEnded aufgerufen werden?
Neoneye
3
@ JasonSilberman Sie vergessen #import <UIKit / UIGestureRecognizerSubclass.h>
LE SANG
Wie würden Sie Double Tap damit implementieren?
Babiker
1
@etayluz Sie haben Status zuweisen, um zu beginnen und zu beenden. Überprüfen Sie dann den Status im Griff. Benutzerdefinierte Erkennung bedeutet, dass Sie alles steuern können.
LE SANG
183

Verwenden Sie einen UILongPressGestureRecognizer und setzen minimumPressDurationSie ihn auf 0. Er verhält sich während des UIGestureRecognizerStateBeganStatus wie ein Touchdown .

Für 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()
    }
}

Für Ziel-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 Kümmel
quelle
4
Für Leute, die Interface Builder (ich) mögen, kann MinimumPressDuration auch in IB eingestellt werden (danke Rob für die großartige Lösung)
tmr
Gibt es hier eine Möglichkeit, Nachbesserungen zu erkennen?
CalZone
1
@ CalZone Ja, überprüfen Sie den GestenstatusUIGestureRecognizerStateEnded
Rob Caraway
2
Eine schöne Problemumgehung +1. minimumPressDurationkann 0 tho sein.
Valentin Radu
1
GEFAHR Es kann oft mehrmals mit einer typischen Note aufgerufen werden.
Fattie
22

Schnell (ohne Unterklasse)

Hier ist eine Swift-Version ähnlich der Objective-C-Antwort von Rob Caraway .

Die Idee ist, einen Gestenerkenner mit langem Drücken zu verwenden, der minimumPressDurationauf Null gesetzt ist, anstatt einen Gestenerkenner zu verwenden. Dies liegt daran, dass der Gestenerkenner für langes Drücken Berührungsereignisse meldet, während die Tippgeste dies nicht tut.

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
quelle
1
@richy, es fühlte sich auch für mich ein bisschen wie ein Hack an, da der Name eine lange Gestenerkennung ist, aber diese Methode ist viel einfacher als das Unterteilen der Ansicht und wie Sie sagten, funktioniert sie großartig.
Suragch
Diese Lösung verursacht ein Bildlaufproblem in UIscrollView-Unterklassen
SPatel
Ich denke, Bater Ansatz ist Custom Gesture
Recognizer
@Suragch Ich bin auf dasselbe Problem gestoßen, auf das ich mit der Antwort von lessing gestoßen bin. Diese Antwort funktioniert, aber ich habe die Fähigkeit zum Wischen verloren, da ich denke, dass es eine Berührung ist, sobald ich meinen Finger auf das Objekt lege, um es zu wischen. Wie kann es zwischen beiden unterscheiden?
Lance Samaria
@LanceSamaria, Wenn Sie eine komplexere Berührungserkennung benötigen als nur tippen, würde ich einen benutzerdefinierten Gestenerkenner verwenden.
Suragch
1

Dies ist eine andere Lösung. Erstellen Sie eine Unterklasse von UIControl. Sie können es wie UIView auch in Storyboard verwenden, da UIControl eine Unterklasse von UIView ist.

class TouchHandlingView: UIControl {
}

Und addTarget dazu:

@IBOutlet weak var mainView: TouchHandlingView!
...

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

Dann wird die festgelegte Aktion wie UIButton aufgerufen:

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

Ich brauchte die Fähigkeit für meine Ansicht, einen Haarauslöser zu haben, damit er reagiert, sobald er getippt wird. Die Verwendung der beiden @ LESANG-Antworten funktionierte ebenso wie die Verwendung der @ RobCaraway-Antwort . Das Problem, auf das ich bei beiden Antworten stieß, war, dass ich nicht mehr in der Lage war, Wischbewegungen zu erkennen. Ich musste meine Ansicht drehen, wenn ich wischte, aber sobald mein Finger die Ansicht berührte, wurde nur das Tippen erkannt. Der tapRecognizer war zu empfindlich und konnte nicht zwischen einem Tap und einem Swipe unterscheiden.

Dies ist, was ich basierend auf der @ LESANG-Antwort in Kombination mit dieser Antwort und dieser Antwort gefunden habe .

Ich habe in jeder Veranstaltung 6 Kommentare abgegeben.

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

Und um es so zu verwenden, dass die Ansicht, die es verwendet, die Haarauslöserreaktion und die Wischgesten nach links und rechts erhält:

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
quelle