iOS/Swift:触摸事件在UIButton上触发时如何实现视图上下滚动?
解决方案
该问题的根本原因是UIButton默认会优先响应触摸事件,拦截事件向父层UIScrollView(包括子类UITableView、UICollectionView)的传递,导致仅当触摸起始点在按钮外时才能识别滚动手势。你可以按需选择以下方案实现需求:
- 方案1:调整UIScrollView手势属性(代码量最少,优先推荐)
修改UIScrollView的两个内置属性,允许滚动手势取消子视图的触摸响应:
yourScrollView.delaysContentTouches = true yourScrollView.canCancelContentTouches = true
两个属性的作用:
delaysContentTouches为true时,滚动视图会延迟150ms判断用户操作是否为滚动手势,再决定是否把事件传递给子视图;canCancelContentTouches为true时,已经传递给子视图的触摸事件如果被判定为滚动操作,会自动取消子视图的响应,交给滚动视图处理。
- 方案2:实现手势代理协议,允许多手势同时识别(不影响按钮原有点击逻辑,交互最自然)
该方案可以实现轻触按钮正常触发点击、按住按钮滑动正常触发滚动的效果,符合系统原生交互逻辑:
- 给滚动视图的平移手势设置代理:
yourScrollView.panGestureRecognizer.delegate = self
- 实现
UIGestureRecognizerDelegate代理方法:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { // 仅当两个手势分别是滚动视图平移手势、按钮的点击手势时,允许同时识别 guard gestureRecognizer == yourScrollView.panGestureRecognizer, otherGestureRecognizer.view is UIButton else { return false } return true }
- 方案3:自定义UIButton重写触摸响应(适合需要深度定制按钮交互的场景)
如果你需要完全控制按钮的事件传递逻辑,可以继承UIButton重写hitTest方法:
class ScrollFriendlyButton: UIButton { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let scrollSuperview = sequence(first: self, next: { $0.superview }) .first(where: { $0 is UIScrollView }) as? UIScrollView else { return super.hitTest(point, with: event) } // 如果当前已经识别到滚动动作,就不响应按钮的触摸 if scrollSuperview.panGestureRecognizer.state == .began || scrollSuperview.panGestureRecognizer.state == .changed { return nil } return super.hitTest(point, with: event) } }
所有方案对UIScrollView的子类(UITableView、UICollectionView、UITextView等)都完全适用。
内容的提问来源于stack exchange,提问作者Giel Berkers
相关产品推荐
相关产品推荐

