如何阻止触摸事件传递至下层视图?UIScrollView手势冲突问题
解决方案
方法一:在ScrollView触摸方法中过滤子视图区域的触摸
重写自定义UIScrollView的touchesBegan、touchesMoved等方法,先判断触摸点是否落在带有UIPanGestureRecognizer的子视图范围内,若是则跳过所有自定义处理逻辑(也可根据需求选择是否调用super方法)。
步骤:
- 添加辅助方法判断触摸点是否在目标子视图上:
private func isTouchOnPanEnabledSubview(_ touch: UITouch) -> Bool { let touchLocation = touch.location(in: self) // 遍历子视图,检查是否包含UIPan手势且触摸点在其范围内 for subview in subviews { guard let gestures = subview.gestureRecognizers else { continue } let hasPanGesture = gestures.contains(where: { $0 is UIPanGestureRecognizer }) guard hasPanGesture else { continue } let locationInSubview = touch.location(in: subview) if subview.point(inside: locationInSubview, with: nil) { return true } } return false }
- 在重写的触摸方法中做过滤:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, !isTouchOnPanEnabledSubview(touch) else { // 触摸在子视图上时直接返回,不执行任何自定义逻辑 return } // 你的自定义触摸处理逻辑 // ... } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, !isTouchOnPanEnabledSubview(touch) else { return } // 你的自定义触摸处理逻辑 // ... }
方法二:利用手势识别器代理拦截事件传递
给子视图的UIPanGestureRecognizer设置代理,结合delaysTouchesBegan属性延迟向ScrollView传递触摸事件,让手势优先完成识别判断。
步骤:
- 配置子视图的Pan手势:
// 子视图初始化或配置阶段 let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) panGesture.cancelsTouchesInView = true panGesture.delaysTouchesBegan = true // 关键:延迟向视图传递触摸事件,直到手势确定是否识别 panGesture.delegate = self subview.addGestureRecognizer(panGesture)
- 实现手势代理方法:
extension YourViewController: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { // 阻止与ScrollView原生手势同时识别 if otherGestureRecognizer.view is UIScrollView { return false } return true } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { // 确保只有触摸在子视图范围内时,手势才启动识别 let touchPoint = gestureRecognizer.location(in: gestureRecognizer.view) return gestureRecognizer.view?.point(inside: touchPoint, with: nil) ?? false } }
方法三:重写ScrollView的hitTest方法优化事件路径
通过重写hitTest方法,直接返回带有Pan手势的子视图作为第一响应者,减少ScrollView接收触摸事件的概率,需配合方法一使用以完全屏蔽自定义逻辑。
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let hitView = super.hitTest(point, with: event) // 如果命中带Pan手势的子视图,直接返回它作为第一响应者 if let hitView = hitView, hitView.gestureRecognizers?.contains(where: { $0 is UIPanGestureRecognizer }) == true { return hitView } return super.hitTest(point, with: event) }
注意事项
- 方法一最直接,可完全屏蔽ScrollView在子视图区域的触摸逻辑执行;
- 方法二通过手势延迟和代理,减少ScrollView接收到的触摸事件次数;
- 方法三优化事件传递路径,但无法完全阻止ScrollView收到初始触摸事件,需配合方法一使用。
内容的提问来源于stack exchange,提问作者Son Nguyen
相关产品推荐
相关产品推荐

