UITapGestureRecognizer - заставить его работать при касании, а не при касании?

84

То, для чего я использую событие касания, очень чувствительно ко времени, поэтому мне любопытно, можно ли активировать UITapGestureRecognizer, когда пользователь просто коснется земли, вместо того, чтобы требовать, чтобы они тоже подправили?

user212541
источник
1
Если это помогает, в UITouch есть метод touchesStarted. Но здесь не используются распознаватели жестов, как вы просили.
vqdave

Ответы:

141

Создайте собственный подкласс 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)
}

Тестирование показывает, что он не будет вызываться более одного раза. Он работает как прямая замена; вы можете просто переключаться между двумя вызовами.

LE SANG
источник
10
дополнительный +1 для импорта "UIGestureRecognizerSubclass.h". Ницца.
Себастьян Ходжас
3
Разве super не следует вызывать в методах touchBegin, touchesMoved, touchesEnded?
neoneye
3
@JasonSilberman, вы забыли #import <UIKit / UIGestureRecognizerSubclass.h>
LE SANG
Как бы вы с этим реализовали двойное нажатие?
Бабикер
1
@etayluz у вас есть назначить состояние для начала и конца. Затем проверьте его состояние в ручке. Пользовательский распознаватель означает, что вы можете контролировать все.
LE SANG 02
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
для людей, которым нравится конструктор интерфейсов (я), в IB также можно установить minimumPressDuration (спасибо, Роб, за отличное решение)
tmr
Есть ли здесь способ обнаружить прикосновение?
CalZone
1
@CalZone Да, проверьте состояние жестовUIGestureRecognizerStateEnded
Роб Карауэй,
2
Хороший обходной путь +1. minimumPressDurationможет быть 0
Валентин Раду
1
ОПАСНО его часто можно назвать более одного раза с типичным прикосновением.
Fattie
23

Swift (без подкласса)

Вот версия 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")
        }
    }
}
Suragch
источник
1
@richy, Мне тоже это показалось хаком, так как имя - распознаватель жестов при длительном нажатии, но этот метод намного проще, чем создание подкласса представления, и, как вы сказали, он отлично работает.
Suragch 06
Это решение вызывает проблему с прокруткой в ​​подклассах
UIscrollView
Я думаю, что подход батера - это распознаватель Custom Gesture
SPatel 09
@Suragch Я столкнулся с той же проблемой, с которой столкнулся с ответом Лессинга. Этот ответ работает, но я потерял способность смахивать, потому что, как только я кладу палец на объект, чтобы провести по нему, он думает, что это касание. Как он может различать их обоих?
Лэнс Самария
1
@LanceSamaria, если вам нужно более сложное распознавание касаний, чем просто касание, я бы использовал пользовательский распознаватель жестов.
Suragch
1

Это другое решение. Создайте подкласс UIControl. Вы можете использовать его как UIView даже в раскадровке, потому что 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")
}
Morizotter
источник
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)
Копье Самария
источник