自定义UIGestureRecognizer双指手势:如何放宽同时触摸识别要求
解决自定义UIGestureRecognizer双指同步识别过严的问题
嘿,这个痛点我太熟悉了!系统默认的触摸事件处理逻辑对双指同步的要求确实有点苛刻,毕竟它得优先避免误触,但咱们完全可以通过自定义逻辑来放宽这个限制,让手势识别更“宽容”。给你几个实用的方案:
核心思路:不要在touchesBegan里死磕同步性
系统的touchesBegan只会触发新触摸的第一次按下事件,双指哪怕差个几十毫秒,都会分成两次touchesBegan调用。所以咱们不能刚收到一个触摸就直接判定失败,得给第二个触摸留个“缓冲时间”,或者在后续事件里持续检查触摸数量。
方案一:延迟检查+自定义触摸集合
自己维护一个活跃触摸的集合,在touchesBegan里先记录触摸,只有一个触摸时设置短延迟(比如100-150ms),超时前如果第二个触摸进来,就触发手势;如果超时还没第二个触摸,再重置状态。同时在touchesMoved里也做检查,避免用户先按下一个手指再滑动着按下第二个的情况。
示例代码:
class TwoFingerGestureRecognizer: UIGestureRecognizer { private var activeTouches = Set<UITouch>() private var delayCheckTask: DispatchWorkItem? override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { super.touchesBegan(touches, with: event) activeTouches.formUnion(touches) // 直接凑齐双指,立刻启动手势 if activeTouches.count == 2 { startGesture() return } // 只有单指,启动延迟检查 if activeTouches.count == 1 { let task = DispatchWorkItem { [weak self] in guard let self = self else { return } if self.activeTouches.count == 2 { self.startGesture() } else { // 超时无第二指,重置状态 self.reset() } } delayCheckTask = task // 120ms的缓冲,这个值可以根据测试调整 DispatchQueue.main.asyncAfter(deadline: .now() + 0.12, execute: task) } } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) { super.touchesMoved(touches, with: event) // 手势还未启动时,检查是否已经凑齐双指 if state == .possible && activeTouches.count == 2 { startGesture() } // 这里可以添加手势进行中的逻辑(比如计算双指位移、夹角等) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) { super.touchesEnded(touches, with: event) activeTouches.subtract(touches) if state == .began || state == .changed { // 双指剩一个,结束手势 if activeTouches.count < 2 { state = .ended } } else { // 还没启动手势就抬起手指,取消延迟任务 delayCheckTask?.cancel() reset() } } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) { super.touchesCancelled(touches, with: event) activeTouches.subtract(touches) delayCheckTask?.cancel() reset() } private func startGesture() { delayCheckTask?.cancel() state = .began } override func reset() { super.reset() activeTouches.removeAll() delayCheckTask?.cancel() delayCheckTask = nil } }
方案二:参考系统手势的逻辑(比如UIPinchGestureRecognizer)
系统自带的捏合手势完全允许双指不同步按下,它的核心逻辑是持续跟踪触摸数量,只要数量达到要求就启动识别,而不是在touchesBegan里一次性要求双指同步。你可以模仿这个思路:
- 始终保持对当前活跃触摸的计数
- 当触摸数量≥2时,进入手势识别状态
- 触摸数量<2时,结束或重置手势
这种方式不需要延迟,而是实时响应触摸数量的变化,体验更流畅。
注意事项
- 延迟时间别太长:超过200ms用户会感觉到明显的卡顿,100-150ms是比较平衡的区间,你可以根据自己的手势场景调整
- 处理触摸取消:比如用户按下一个手指后,系统触发了触摸取消(比如弹出通知),一定要及时清空触摸集合和取消延迟任务,避免内存泄漏或错误的手势触发
- 测试不同的操作场景:比如用户先按一个手指,滑动一段距离再按第二个;或者两个手指先后快速按下,确保这些场景都能正常识别
内容的提问来源于stack exchange,提问作者Melodius
相关产品推荐
相关产品推荐

