如何加速识别单击而不是双击?
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)
//
// 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)
}
}
我有一个 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)
//
// 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)
}
}