UIScrollView手势冲突:touchesBegan偶现不触发致绘制时页面滚动
问题根因
核心问题是UIScrollView的内置平移手势存在事件拦截优先级:系统会给pan手势预留一段识别判定窗口,在窗口期间触摸事件不会传递到下层子视图,你写在子视图touchesBegan里的禁用滚动逻辑存在时序滞后,无法覆盖手势识别的抢跑阶段。
偶现的触发逻辑非常明确:
- 若触摸后停留时间较长、初始移动位移未达到scrollView的滚动触发阈值,pan手势识别失败,事件才会下发到子视图触发
touchesBegan,此时禁用滚动的逻辑正常执行,画笔可以正常绘制 - 若触摸后短时间内产生滑动位移,pan手势会在子视图接收到事件前识别成功,直接触发滚动逻辑,子视图的
touchesBegan完全不会被调用,禁用滚动的代码自然不会执行,就会出现滑动打断绘制的异常
可行解决方案
- 最优方案:工具切换时直接修改scrollView状态,不要等触摸触发再调整
当用户选中画笔、橡皮擦工具时,立刻执行scrollView.isScrollEnabled = false、scrollView.panGestureRecognizer.isEnabled = false;当用户切回浏览、选择等非绘制工具时,再重新开启scrollView的滚动能力,从根源上消除手势抢事件的时序问题。 - 方案2:通过手势代理调整识别优先级
不要依赖touchesBegan系列方法处理绘制逻辑,给Page视图绑定自定义的UIPanGestureRecognizer专门处理绘制,实现UIGestureRecognizerDelegate协议控制手势优先级:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { guard let pagePan = gestureRecognizer as? UIPanGestureRecognizer, pagePan.view is Page, (currentTool == .PEN || currentTool == .ERASER), otherGestureRecognizer == scrollView.panGestureRecognizer else { return false } // 绘制场景下,要求scrollView的滚动手势必须等绘制手势识别失败后才能触发 return true }
- 方案3:重写hitTest拦截事件传递
自定义UIScrollView子类,重写hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?方法,当触摸点落在Page视图上、且当前处于绘制工具模式时,直接返回对应的Page视图,阻断scrollView对事件的拦截。
内容的提问来源于stack exchange,提问作者KindestPath0b
相关产品推荐
相关产品推荐

