如何加速识别单击而不是双击?

How to accelerate the identification of a single tap over a double tap?

我有一个 UITableView 行,我在其中添加了单击 双击手势:

let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTap:")
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
        
let singleTap = UITapGestureRecognizer(target: self, action: "singleTap:")
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
singleTap.requireGestureRecognizerToFail(doubleTap)

tableView.addGestureRecognizer(doubleTap)
tableView.addGestureRecognizer(singleTap)

有没有办法缩短第一次点击与手势识别器意识到这是单击而不是双击之间的时间?

我问这个是因为当我单击时,新的 viewController 出现得很晚,给人一种应用程序滞后的感觉。

我在这个 link

上找到了答案

swift版本:

class UIShortTapGestureRecognizer: UITapGestureRecognizer {
    let tapMaxDelay: Double = 0.3

    override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
        super.touchesBegan(touches, withEvent: event)
        delay(tapMaxDelay) {
            // Enough time has passed and the gesture was not recognized -> It has failed.
            if  self.state != UIGestureRecognizerState.Ended {
                self.state = UIGestureRecognizerState.Failed
            }
        }
    }
}

delay(delay: Double, closure:()->()):

class func delay(delay:Double, closure:()->()) {
        dispatch_after(dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)
    }

为了将来由 Howard Yang 全面实施,这里有链接: https://github.com/oney/SingleDoubleTapGestureRecognizer

 let tap = SingleDoubleTapGestureRecognizer(target: self, singleAction: Selector("singleTap"), doubleAction: Selector("doubleTap"))
        tap.duration = 0.8
        view.addGestureRecognizer(tap)

https://github.com/oney/SingleDoubleTapGestureRecognizer/blob/master/Pod/Classes/SingleDoubleTapGestureRecognizer.swift

//
//  SingleDoubleTapGestureRecognizer.swift
//  SingleDoubleTapGestureRecognizer
//
//  Created by Howard Yang on 08/22/2015.
//  Copyright (c) 2015 Howard Yang. All rights reserved.
//

import UIKit

public class SingleDoubleTapGestureRecognizer: UITapGestureRecognizer {
    var targetDelegate: SingleDoubleTapGestureRecognizerDelegate
    public var duration: CFTimeInterval = 0.3 {
        didSet {
            self.targetDelegate.duration = duration
        }
    }
    public init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
        targetDelegate = SingleDoubleTapGestureRecognizerDelegate(target: target, singleAction: singleAction, doubleAction: doubleAction)
        super.init(target: targetDelegate, action: Selector("fakeAction:"))
        numberOfTapsRequired = 1
    }
}
class SingleDoubleTapGestureRecognizerDelegate: NSObject {
    var target: AnyObject
    var singleAction: Selector
    var doubleAction: Selector
    var duration: CFTimeInterval = 0.3
    var tapCount = 0

    init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
        self.target = target
        self.singleAction = singleAction
        self.doubleAction = doubleAction
    }

    func fakeAction(g: UITapGestureRecognizer) {
        tapCount = tapCount + 1
        if tapCount == 1 {
            delayHelper(duration, task: {
                if self.tapCount == 1 {
                    NSThread.detachNewThreadSelector(self.singleAction, toTarget:self.target, withObject: g)
                }
                else if self.tapCount == 2 {
                    NSThread.detachNewThreadSelector(self.doubleAction, toTarget:self.target, withObject: g)
                }
                self.tapCount = 0
            })
        }
    }
    typealias DelayTask = (cancel : Bool) -> ()

    func delayHelper(time:NSTimeInterval, task:()->()) ->  DelayTask? {

        func dispatch_later(block:()->()) {
            dispatch_after(
                dispatch_time(
                    DISPATCH_TIME_NOW,
                    Int64(time * Double(NSEC_PER_SEC))),
                dispatch_get_main_queue(),
                block)
        }

        var closure: dispatch_block_t? = task
        var result: DelayTask?

        let delayedClosure: DelayTask = {
            cancel in
            if let internalClosure = closure {
                if (cancel == false) {
                    dispatch_async(dispatch_get_main_queue(), internalClosure);
                }
            }
            closure = nil
            result = nil
        }

        result = delayedClosure

        dispatch_later {
            if let delayedClosure = result {
                delayedClosure(cancel: false)
            }
        }

        return result;
    }

    func cancel(task:DelayTask?) {
        task?(cancel: true)
    }
}

完全实施 Markus's Swift 3 version of eladleb's 原始解决方案。


创建子类文件UIShortTapGestureRecogninzer

import UIKit
import UIKit.UIGestureRecognizerSubclass 

class UIShortTapGestureRecognizer: UITapGestureRecognizer {
    let tapMaxDelay: Double = 0.3 //anything below 0.3 may cause doubleTap to be inaccessible by many users

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)

        DispatchQueue.main.asyncAfter(deadline: .now() + tapMaxDelay) { [weak self] in
            if self?.state != UIGestureRecognizerState.recognized {
                self?.state = UIGestureRecognizerState.failed
            }
        }
    }
}

注意:添加 UIGestureRecognizer 时,只需要 doubleTap 类型为 UIShortTapGestureRecognizer & singleTap.require(toFail: doubleTap) 是必需的。

func addBoth (views: UIView, selectorSingle: Selector, selectorDouble: Selector) {
    let doubleTap:UIShortTapGestureRecognizer = UIShortTapGestureRecognizer(target: self, action: selectorDouble)
    doubleTap.numberOfTapsRequired = 2
    views.addGestureRecognizer(doubleTap)

    let singleTap:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: selectorSingle)
    singleTap.numberOfTapsRequired = 1
    singleTap.require(toFail: doubleTap)
    views.addGestureRecognizer(singleTap)
}

我宁愿推荐使用 canBePrevented(by:) 功能,它考虑了要执行的点击次数并且不会 运行 你双击手势识别器,除非第一个是 recognized/failed. 可以预防(通过:)

受 Howard Yang 的实施启发,Swift 5.1 使用 DispatchWorkItem:

public class SingleDoubleTapGestureRecognizer: UITapGestureRecognizer {
    var targetDelegate: SingleDoubleTapGestureRecognizerDelegate
    public var timeout: TimeInterval = 0.3 {
        didSet {
            self.targetDelegate.timeout = timeout
        }
    }

    public init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
        targetDelegate = SingleDoubleTapGestureRecognizerDelegate(target: target, singleAction: singleAction, doubleAction: doubleAction)
        super.init(target: targetDelegate, action: #selector(targetDelegate.recognizerAction(recognizer:)))
    }
}

class SingleDoubleTapGestureRecognizerDelegate: NSObject {
    weak var target: AnyObject?
    var singleAction: Selector
    var doubleAction: Selector
    var timeout: TimeInterval = 0.3
    var tapCount = 0
    var workItem: DispatchWorkItem?

    init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
        self.target = target
        self.singleAction = singleAction
        self.doubleAction = doubleAction
    }

    @objc func recognizerAction(recognizer: UITapGestureRecognizer) {
        tapCount += 1
        if tapCount == 1 {
            workItem = DispatchWorkItem { [weak self] in
                guard let weakSelf = self else { return }
                weakSelf.target?.performSelector(onMainThread: weakSelf.singleAction, with: recognizer, waitUntilDone: false)
                weakSelf.tapCount = 0
            }
            DispatchQueue.main.asyncAfter(
                deadline: .now() + timeout,
                execute: workItem!
            )
        } else {
            workItem?.cancel()
            DispatchQueue.main.async { [weak self] in
                guard let weakSelf = self else { return }
                weakSelf.target?.performSelector(onMainThread: weakSelf.doubleAction, with: recognizer, waitUntilDone: false)
                weakSelf.tapCount = 0
            }
        }
    }
}

用法:

let singleDoubleTapRecognizer = SingleDoubleTapGestureRecognizer(
    target: self,
    singleAction: #selector(handleTapGesture),
    doubleAction: #selector(handleDoubleTapGesture)
)

Swift 5 实施 Nico 接受的答案。

class UIShortTapGestureRecognizer: UITapGestureRecognizer {
    var maximumTapLength: Double = 0.3

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        delay(delay: maximumTapLength) {
            // Enough time has passed and the gesture was not recognized -> It has failed.
            if  self.state != .ended {
                self.state = .failed
            }
        }
    }

    func delay(delay:Double, closure:@escaping ()->()) {
        DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure)
    }
}