如何创建可响应点击执行动作并透传点击事件至后方视图的通用UIView子类
通用透传点击UIView实现方案
核心实现逻辑是通过重写hitTest(_:with:)方法,在事件传递的判定阶段完成两个目标:检测到点击落在自身区域时触发绑定的自定义动作,同时不将自身作为命中视图拦截事件,让系统将事件正常传递给层级位于下方的其他视图。
实现的类特性
- 完全不可见:默认配置为无背景色、不做额外渲染、无自定义图层内容,不会遮挡下层视图的显示
- 动作触发:检测到点击落在自身定义的触摸判定范围内时,执行绑定的自定义回调
- 事件透传:检测到点击后不拦截事件,由系统正常完成后续的视图命中流程,将点击传递给下层的兄弟视图、子视图
- 防重复触发:通过事件ID标记避免单次触摸过程中多次触发绑定动作
完整代码实现
final class PassthroughTapView: UIView { // 绑定的点击触发动作 var onTap: (() -> Void)? // 记录已触发动作的事件ID,避免单次触摸重复回调 private var triggeredEventIDs = Set<Int>() override init(frame: CGRect) { super.init(frame: frame) // 配置不可见的默认状态 isOpaque = false backgroundColor = .clear layer.drawsAsynchronously = true } required init?(coder: NSCoder) { super.init(coder: coder) isOpaque = false backgroundColor = .clear layer.drawsAsynchronously = true } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { // 仅当触摸点在自身判定范围、且视图可交互时做逻辑处理 guard point(inside: point, with: event), isUserInteractionEnabled else { // 不满足条件时走系统默认逻辑 return super.hitTest(point, with: event) } // 处理单次触摸的动作触发 if let event = event { let eventID = event.hashValue if !triggeredEventIDs.contains(eventID) { triggeredEventIDs.insert(eventID) // 执行绑定的自定义动作 onTap?() // 延迟清理事件ID,覆盖完整触摸周期避免重复触发 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in self?.triggeredEventIDs.remove(eventID) } } } // 关键:返回nil不拦截事件,让系统继续遍历下层兄弟视图完成命中 return nil } }
使用注意事项
- 直接将该视图作为子视图添加到需要触发动作的区域上层即可,不需要为其添加额外的手势识别器
- 视图本身的子视图将不会接收到任何点击事件,所有事件会直接透传给下层的兄弟视图
- 如需调整触摸判定范围,可以重写
point(inside:with:)方法,自定义扩大、缩小的响应区域 - 该实现不会影响下层视图的手势识别、按钮点击等正常交互流程,事件传递逻辑和系统原生流程完全一致
内容的提问来源于stack exchange,提问作者Tamir Nahum
相关产品推荐
相关产品推荐

