iOS 手势识别与滚动冲突处理
iOS 手势识别与滚动冲突处理
横向卡片嵌套纵向列表、侧滑返回与轮播手势并存、可拖动面板覆盖按钮时,多个手势识别器可能争夺同一次触摸。解决冲突的关键不是让某个识别器永远优先,而是根据方向、视图层级和业务状态定义失败关系与同时识别规则。
一、理解识别器状态
手势识别器会经历可能、开始、变化、结束、取消和失败等状态。离散手势通常从可能直接到结束,连续手势则会经过开始和变化。
业务逻辑要处理取消状态,不能假设开始的拖动一定正常结束。页面转场或父级接管时,当前操作可能被取消。
二、先利用系统组件
滚动视图已经包含拖动和缩放识别器。自定义分页、下拉刷新和返回手势前,应优先复用项目已有容器能力。
在系统滚动手势上再叠加一个功能相近的平移识别器,容易造成两套位移同时更新。
三、通过代理限制起始条件
final class DrawerGestureController: NSObject, UIGestureRecognizerDelegate {
private let panGesture = UIPanGestureRecognizer()
func gestureRecognizerShouldBegin(
_ gestureRecognizer: UIGestureRecognizer
) -> Bool {
guard let pan = gestureRecognizer as? UIPanGestureRecognizer,
let view = pan.view else {
return false
}
let velocity = pan.velocity(in: view)
return abs(velocity.x) > abs(velocity.y)
}
}
开始前判断方向,比手势已经开始后再强行停止更稳定。还可以结合面板是否允许打开、滚动位置等业务条件。
四、定义失败依赖
当一个手势必须确认失败后另一个才能开始,可以建立失败依赖。例如边缘返回应优先于页面内部横向拖动时,让内部手势等待系统返回手势判断。
依赖方向错误会让常用手势产生明显延迟。只有两者确实互斥且优先级固定时才使用。
系统导航手势属于平台行为,尽量不要完全替换或禁用。自定义导航容器需要完整验证交互返回和转场状态。
五、允许合理的同时识别
缩放与旋转、父面板与子滚动在某些阶段可以同时识别:
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
return isSupportedPair(gestureRecognizer, otherGestureRecognizer)
}
不要无条件返回 true。应检查具体识别器组合和视图关系,否则点击、滑动和返回可能同时触发。
六、处理滚动边界交接
底部面板内嵌列表时,列表未到顶部应由列表滚动;列表到顶部且用户继续下拉,才把位移交给面板。
需要同时观察内容偏移、拖动方向和面板状态,并在一次手势内保持稳定。处理交接时避免反复修改滚动偏移造成抖动。
七、Hit Testing 决定触摸目标
手势冲突有时其实是命中测试问题。透明覆盖视图即使不可见,也可能拦截触摸。检查 isHidden、alpha、isUserInteractionEnabled 和视图层级。
扩大按钮点击区域时,可以重写命中判断,但不能让区域与相邻按钮重叠,也不能让父视图范围外的目标在裁剪后不可达。
八、点击与拖动共存
卡片既能点击又能横向拖动时,应使用系统手势协调,而不是在触摸回调中自行猜测。平移超过阈值后,点击识别器会失败;轻触则正常触发。
cancelsTouchesInView 等属性会影响控件收到的触摸,需要根据是否允许按钮与父手势同时工作进行设置。
九、保持交互状态可恢复
交互式转场中途取消时,视图位置、遮罩、导航栏和状态栏都要恢复。动画结束回调不能只处理完成分支。
控制器离开后移除自定义识别器代理和长期闭包,避免引用环或旧页面仍处理手势。
十、验证手势矩阵
测试应包括:
- 缓慢拖动与快速甩动。
- 水平、垂直和接近对角线方向。
- 子列表位于顶部、中间和底部。
- 交互返回完成与取消。
- 按钮点击、长按和拖动组合。
- 多指加入和离开。
- 横竖屏及不同页面层级。
总结
iOS 手势冲突应通过明确规则协调。先在开始阶段判断方向和业务条件,再按需要设置失败依赖或特定组合的同时识别。滚动边界交接保持稳定,命中测试和取消状态一并处理,才能保留系统手势并实现自然的复合交互。
- 点赞
- 收藏
- 关注作者
评论(0)