UIKit中如何在子视图间传递手势,实现跨边界拖拽检测?
问题原因
iOS系统的触摸事件派发规则里,hitTest(_:with:) 仅会在手指首次按下触发touchesBegan时执行一次,用来确定本次触摸序列的第一响应者。后续手指拖拽移动、抬起的全流程中,所有触摸事件都会固定发送给这个初始命中的视图,不会因为手指滑入其他视图边界就重新执行hitTest切换响应对象,这就是拖拽过程中途经的子视图无法收到事件的核心原因。
实现方案
不需要修改系统事件传递链做手势穿透,直接在根视图上统一跟踪全流程的触摸位置,实时判断当前手指落点覆盖的子视图并触发反馈即可,逻辑稳定且不会和原有点击逻辑冲突。
代码修改
替换你原有的TestView实现即可:
class TestView: UIView { // 记录上一次触摸位置命中的视图,避免重复触发反馈 private var lastHitViews: Set<UIView> = [] override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { // 保留原有点击场景的命中逻辑,不影响正常点击响应 guard isUserInteractionEnabled, !isHidden, alpha > 0.01 else { return nil } guard point(inside: point, with: event) else { return nil } for subview in subviews.reversed() { let convertedPoint = convert(point, to: subview) guard let hitView = subview.hitTest(convertedPoint, with: event) else { continue } return hitView } return self } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) checkCoveredViews(with: touches) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) checkCoveredViews(with: touches) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) lastHitViews.removeAll() } override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesCancelled(touches, with: event) lastHitViews.removeAll() } private func checkCoveredViews(with touches: Set<UITouch>) { guard let touch = touches.first else { return } let touchPoint = touch.location(in: self) var currentHitViews = Set<UIView>() // 遍历所有子视图,判断当前触摸点是否落在视图范围内 for subview in subviews { let pointInSub = convert(touchPoint, to: subview) guard subview.point(inside: pointInSub, with: nil) else { continue } currentHitViews.insert(subview) // 首次进入该视图范围时触发反馈 if !lastHitViews.contains(subview) { subview.backgroundColor = .red } } // 如果需要手指离开视图时恢复原有颜色,可在此处遍历lastHitViews做差集后重置颜色 lastHitViews = currentHitViews } }
注意事项
- 上述代码完全兼容你现有三层叠加视图的布局,从屏幕任意位置按下拖拽,途经的子视图都会在手指进入边界时触发变红反馈
- 如果需要实现手指离开视图后恢复原始背景色,可以给UIView添加关联属性存储初始背景色,在检测到手指离开视图范围时赋值还原即可
- 不要尝试在
touchesMoved阶段强制调用hitTest切换第一响应者,会破坏系统原生事件派发逻辑,引发手势断触、交互冲突等问题
内容的提问来源于stack exchange,提问作者CodeyTown
相关产品推荐
相关产品推荐

