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

Posted

技术标签:

【中文标题】如何加速识别单击而不是双击?【英文标题】:How to accelerate the identification of a single tap over a double tap? 【发布时间】:2015-04-04 20:37:38 【问题描述】:

我有一个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 出现得很晚,给人一种应用程序滞后的感觉。

【问题讨论】:

不,你需要直接处理水龙头来改变时间 我该怎么做?这是“最佳实践”吗? 在单元格上不是微不足道的,可能您会使用实现触摸事件方法的自定义子视图。或者,将您的 UI 更改为不使用双击 【参考方案1】:

我在link找到了答案

快速版本:

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)
    

【讨论】:

不要忘记在定义UITapGestureRecognizer 子类的文件中添加import UIKit.UIGestureRecognizerSubclass。 (来自***.com/a/26557518/341954)否则你会得到构建错误:“方法没有覆盖其超类中的任何方法”【参考方案2】:

完整实现Markus's Swift 3 版本的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 类型需要为 UIShortTapGestureRecognizersingleTap.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)

【讨论】:

【参考方案3】:

对于未来,由 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)
    

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。【参考方案4】:

我宁愿推荐使用canBePrevented(by:) 函数,它考虑了要执行的点击次数,并且不会运行你的双击手势识别器,除非第一个被识别/失败。 canBePrevented(by:)

【讨论】:

【参考方案5】:

受 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)
)

【讨论】:

【参考方案6】:

Nico 接受的答案的 Swift 5 实现。

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)
    

【讨论】:

以上是关于如何加速识别单击而不是双击?的主要内容,如果未能解决你的问题,请参考以下文章

如何识别iOS中的单个单击和双击视图?

8051识别按键单击双击和长按

加速度传感器的原理和应用-手机翻转失重检测运动检测位置识别

加速度传感器的原理和应用-手机翻转失重检测运动检测位置识别

当在 actionPerformed (Java Swing) 上单击按钮时,如何识别是不是选择了 shift?

如果双击否则如何单击 UITapGestureRecognizer