UITapGestureRecognizer - czy działa przy przyłożeniu, a nie przy dotykaniu?

84

To, do czego używam zdarzenia tap, jest bardzo wrażliwe na czas, więc jestem ciekawy, czy możliwe jest aktywowanie UITapGestureRecognizer, gdy użytkownik po prostu dotknie, zamiast wymagać od niego również poprawienia?

user212541
źródło
1
Jeśli to pomoże, UITouch ma metodę touchesStarted. Ale to nie jest użycie rozpoznawania gestów, jak prosiłeś.
vqdave

Odpowiedzi:

139

Utwórz własną podklasę TouchDownGestureRecognizer i zaimplementuj gesty w dotknięciach

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

realizacja:

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

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

Szybka realizacja:

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

Oto składnia Swift dla 2017 do wklejenia:

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

Zwróć uwagę, że jest to zamiennik typu drop-in UITap. Więc w kodzie takim jak ...

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

możesz bezpiecznie przełączyć się na ....

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

Testy pokazują, że nie będzie to wywoływane więcej niż raz. Działa jako zamiennik typu drop-in; możesz po prostu przełączać się między dwoma połączeniami.

LE SANG
źródło
10
dodatkowe +1 dla importu „UIGestureRecognizerSubclass.h”. Ładny.
Sebastian Hojas
3
Czy super nie powinno być wywoływane w metodzie dotknięciaBegin, touchMoved, touchesEnded?
neoneye
3
@JasonSilberman zapomniałeś #import <UIKit / UIGestureRecognizerSubclass.h>
LE SANG
Jak zaimplementowałbyś w tym podwójne dotknięcie?
Babiker
1
@etayluz masz przypisany stan na początek i koniec. Następnie sprawdź stan w uchwycie. Niestandardowy moduł rozpoznawania oznacza, że ​​możesz kontrolować wszystko.
LE SANG,
184

Użyj UILongPressGestureRecognizer i ustaw jego minimumPressDurationwartość na 0. Będzie działać jak przyziemienie podczas UIGestureRecognizerStateBeganstanu.

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

W przypadku 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
źródło
4
dla osób, które lubią konstruktora interfejsu (ja), minimalny czas trwania prasy można również ustawić w IB (dziękuję Rob za świetne rozwiązanie)
tmr
Czy jest tu sposób na wykrycie dotyku?
CalZone
1
@CalZone Tak, sprawdź stan gestówUIGestureRecognizerStateEnded
Rob Caraway
2
Fajne obejście +1. minimumPressDurationmoże wynosić 0 tys.
Valentin Radu
1
NIEBEZPIECZEŃSTWO często można go nazwać więcej niż jeden raz, z typowym dotykiem.
Fattie
23

Swift (bez podklas)

Oto wersja Swift podobna do odpowiedzi Objective-C Roba Carawaya .

Chodzi o to, aby użyć aparatu rozpoznawania gestów długiego naciśnięcia z minimumPressDurationustawieniem na zero, zamiast używać rozpoznawania gestów dotknięcia. Dzieje się tak, ponieważ aparat rozpoznawania gestów długiego naciśnięcia zgłasza zdarzenia związane z dotknięciem, podczas gdy gest dotknięcia nie.

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
źródło
1
@richy, Dla mnie też to trochę jak włamanie, ponieważ nazwa to rozpoznawanie gestów długiego naciśnięcia, ale ta metoda jest o wiele łatwiejsza niż podklasy widoku i jak powiedziałeś, działa świetnie.
Suragch
To rozwiązanie powoduje problem z przewijaniem w podklasach
UIscrollView
Myślę, że podejście
bater to
@Suragch Natknąłem się na ten sam problem, na który natknąłem się z odpowiedzią Lessinga. Ta odpowiedź działa, ale straciłem możliwość przesuwania, ponieważ gdy tylko kładę palec na obiekcie, aby go przesunąć, wydaje mi się, że to dotyk. Jak może odróżnić je obie?
Lance Samaria
1
@LanceSamaria, Jeśli potrzebujesz bardziej złożonego rozpoznawania dotyku niż tylko stuknięcie, użyłbym niestandardowego rozpoznawania gestów.
Suragch
1

To jest inne rozwiązanie. Utwórz podklasę UIControl. Możesz go używać jak UIView nawet w Storyboard, ponieważ UIControl jest podklasą UIView.

class TouchHandlingView: UIControl {
}

I dodaj do niego cel:

@IBOutlet weak var mainView: TouchHandlingView!
...

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

Następnie wyznaczona akcja zostanie nazwana jak UIButton:

func startAction(sender: AnyObject) {
    print("start")
}
morizotter
źródło
1

Potrzebowałem zdolności do tego, aby mój widok miał spust włosa, więc gdy tylko zostanie dotknięty, zareaguje. Używanie zarówno odpowiedzi @LESANG działało, jak i odpowiedzi @RobCaraway . Problem, na który natknąłem się przy obu odpowiedziach, polegał na tym, że straciłem zdolność rozpoznawania przeciągnięć. Potrzebowałem, aby mój widok obracał się po przesunięciu, ale gdy tylko mój palec dotknął widoku, rozpoznawano tylko kran. TapRecognizer był zbyt czuły i nie mógł odróżnić stuknięcia od machnięcia.

Oto, co wymyśliłem na podstawie odpowiedzi @LESANG w połączeniu z tą odpowiedzią i tą odpowiedzią .

W każdym przypadku umieściłem 6 komentarzy.

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

I użyć go tak, aby widok, który go używa, otrzymał odpowiedź spustu włosów i gesty przesunięcia w lewo i w prawo .:

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
źródło