UITapGestureRecognizer - змусити його працювати при дотику, а не до дотику?


84

Те, що я використовую для натискання події, дуже чутливе до часу, тому мені цікаво, чи можливо зробити UITapGestureRecognizer активованим, коли користувач просто натискає, а не вимагати від них також дотику?


1
Якщо це допомагає, UITouch має метод touchesStarted. Але це не використання розпізнавачів жестів, як ви просили.
vqdave

Відповіді:


139

Створіть власний підклас TouchDownGestureRecognizer і впровадьте жест у 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

реалізація:

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

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

Швидке впровадження:

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

Ось синтаксис Swift на 2017 рік для вставки:

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

Зверніть увагу, що це випадаюча заміна для UITap. Отже, в коді, як ...

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

Ви можете сміливо помінятись на ....

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

Тестування показує, що його не буде викликано більше одного разу. Він працює як заміна, що випадає; Ви можете просто переключатися між двома дзвінками.


10
додатковий +1 для імпорту "UIGestureRecognizerSubclass.h". Приємно.
Себастьян Хохас,

3
Чи не слід викликати супер у методах touchesBegin, touchesMoved, touchesEnded?
neoneye

3
@JasonSilberman ти забув #import <UIKit / UIGestureRecognizerSubclass.h>
LE SANG

Як би ви реалізували подвійне натискання з цим?
Babiker

1
@etayluz у вас є призначити стан для початку та кінця. Потім перевірте його стан у дескрипторі. Спеціальний розпізнавач означає, що ви можете контролювати все.
LE SANG

184

Використовуйте UILongPressGestureRecognizer і встановіть для нього minimumPressDurationзначення 0. Це буде діяти як притискання під час UIGestureRecognizerStateBeganстану.

Для 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()
    }
}

Для 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
для людей, яким подобається конструктор інтерфейсів (я), minimumPressDuration також можна встановити в IB (спасибі Роб за чудове рішення)
tmr

Чи є спосіб виявити дотик тут?
CalZone,

1
@CalZone Так, перевірте стан жестівUIGestureRecognizerStateEnded
Роб Каравей

2
Приємний обхідний шлях +1. minimumPressDurationможе становити 0 тис.
Валентин Раду

1
НЕБЕЗПЕЧНО це часто можна назвати більше одного разу, із типовим дотиком.
Fattie

23

Стрімкий (без підкласифікації)

Ось версія Swift, подібна до відповіді Роба Каравея на Objective-C .

Ідея полягає у використанні розпізнавача жестів тривалим натисканням із minimumPressDurationвстановленим на нуль, а не в розпізнаванні жестів натисканням. Це пов’язано з тим, що розпізнавач жестів тривалого натискання повідомляє про події, пов’язані з дотиком, тоді як жест натискання ні.

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, і для мене це було трохи схоже на хакерство, оскільки ім'я - це розпізнавач жестів довгим натисканням, але цей метод набагато простіший, ніж підкласування подання, і, як ви вже сказали, він чудово працює.
Сурач

Це рішення спричиняє проблему прокрутки в підкласах
UIscrollView

Я думаю, що підхід до обробки є призначеним для розпізнавання жестів
SPatel

@Suragch Я зіткнувся з тим самим питанням, що і на відповідь Лессінга. Ця відповідь працює, але я втратив здатність проводити пальцем, тому що, як тільки я кладу пальцем на предмет, щоб провести, він вважає, що це дотик. Як можна розрізнити їх обох?
Lance Samaria

1
@LanceSamaria, Якщо вам потрібне більш складне розпізнавання дотиків, ніж просто натискання, я скористався користувацьким розпізнавачем жестів.
Сурач

1

Це ще одне рішення. Створіть підклас UIControl. Ви можете використовувати його як UIView навіть у Storyboard, оскільки UIControl є підкласом UIView.

class TouchHandlingView: UIControl {
}

І до нього додайте Target:

@IBOutlet weak var mainView: TouchHandlingView!
...

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

Тоді призначена дія буде називатися як UIButton:

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

1

Мені потрібна була можливість для мого зору мати тригер для волосся, щоб, як тільки він натиснув, він реагував. Використання як відповіді @LESANG спрацювало, так і використання відповіді @RobCaraway . Проблема, з якою я зіткнувся з обома відповідями, полягала в тому, що я втратив здатність розпізнавати свайпи. Мені потрібно було, щоб мій погляд обертався при проведенні пальцем, але як тільки палець торкався виду, розпізнавався лише кран. TapRecognizer був надто чутливим і не міг розрізнити натискання та проведення.

Це те, що я придумав на основі відповіді @LESANG у поєднанні з цією відповіддю та цією відповіддю .

Я вкладаю по 6 коментарів до кожного заходу.

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

І використовувати його таким чином, що подання, яке його використовує, отримує реакцію тригера волосся та жести вліво та вправо.

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.