You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义UIGestureRecognizer双击识别时状态重置问题咨询

解决自定义UIGestureRecognizer双击识别时reset()重置状态的问题

我之前也踩过这个坑!问题根源在于你对UIGestureRecognizer的生命周期理解不到位——reset()方法会在手势识别周期结束(无论成功还是失败)时被系统自动调用,用来清理手势的状态。如果第一次点击后你直接让手势进入了结束状态,reset()就会把你的点击计数重置,导致第二次点击完全追踪不到之前的计数。

核心思路调整

系统自带的UITapGestureRecognizer处理双击的逻辑其实很巧妙:第一次点击后,它不会立即结束手势,而是保持在.possible状态,同时启动一个定时器等待第二次点击。如果在超时前收到第二次点击,就判定为双击;超时则判定为单击(或者直接放弃识别)。这样就能避免reset()过早清理状态。

完整实现代码

下面是调整后的自定义双击手势识别器,你可以直接参考使用:

import UIKit

class CustomDoubleTapGesture: UIGestureRecognizer {
    // 记录点击次数
    private var tapCount = 0
    // 用来等待第二次点击的定时器
    private var tapTimer: Timer?
    // 用系统默认的双击间隔,和系统手势体验保持一致
    private let doubleTapInterval = UIGestureRecognizer.defaultDoubleTapInterval
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        
        // 只处理单指点击,多直接标记识别失败
        guard touches.count == 1 else {
            state = .failed
            return
        }
        
        // 如果已有定时器在运行,说明是第二次点击,先取消定时器
        if let existingTimer = tapTimer {
            existingTimer.invalidate()
            tapTimer = nil
        }
        
        tapCount += 1
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesEnded(touches, with: event)
        
        guard touches.count == 1 else {
            state = .failed
            return
        }
        
        switch tapCount {
        case 1:
            // 第一次点击,启动定时器等待第二次点击
            tapTimer = Timer.scheduledTimer(withTimeInterval: doubleTapInterval, repeats: false) { [weak self] _ in
                guard let self = self else { return }
                // 超时后可以选择标记为单击结束,或者直接失败(根据你的需求调整)
                self.state = .ended
                self.tapCount = 0
            }
            // 关键:保持手势处于possible状态,系统不会调用reset()
            state = .possible
            
        case 2:
            // 第二次点击,触发双击成功
            state = .ended
            tapCount = 0
            
        default:
            // 超过两次点击,标记识别失败
            state = .failed
            tapCount = 0
        }
    }
    
    override func reset() {
        super.reset()
        // 清理定时器和计数,避免内存泄漏
        tapTimer?.invalidate()
        tapTimer = nil
        tapCount = 0
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesCancelled(touches, with: event)
        // 手势被取消时也要及时清理状态
        tapTimer?.invalidate()
        tapTimer = nil
        tapCount = 0
        state = .cancelled
    }
}

使用示例

在你的ViewController里这样集成:

override func viewDidLoad() {
    super.viewDidLoad()
    
    let doubleTapGesture = CustomDoubleTapGesture(target: self, action: #selector(handleDoubleTap(_:)))
    view.addGestureRecognizer(doubleTapGesture)
}

@objc private func handleDoubleTap(_ gesture: CustomDoubleTapGesture) {
    if gesture.state == .ended {
        print("双击成功触发!")
    }
}

关键注意点

  1. 保持.possible状态:第一次点击后不要立即把状态设为.ended,否则系统会立刻调用reset()清空你的计数。
  2. 定时器内存管理:一定要用weak self避免循环引用,同时在reset()和touchesCancelled()里主动清理定时器。
  3. 对齐系统体验:使用UIGestureRecognizer.defaultDoubleTapInterval作为双击间隔,让用户的操作感受和系统自带手势一致。

内容的提问来源于stack exchange,提问作者jayOrange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:33:20