Article / 2026/05/29
UIGestureRecognizerDelegate 完全指南
UIGestureRecognizerDelegate 是 iOS 中用于控制手势识别器行为的协议。通过实现该协议的方法,可以精确控制手势的识别时机、优先级和交互关系。
🚀 UIGestureRecognizerDelegate 完全指南
概述
UIGestureRecognizerDelegate 是 iOS 中用于控制手势识别器行为的协议。通过实现该协议的方法,可以精确控制手势的识别时机、优先级和交互关系。
协议方法详解
1. gestureRecognizerShouldBegin(_:)
optional func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
作用: 决定手势识别器是否应该开始识别手势
参数:
gestureRecognizer: 询问是否应该开始的手势识别器
返回值:
true: 允许手势开始识别(默认值)false: 阻止手势开始识别
调用时机: 在手势即将开始识别时调用
使用场景:
- 根据当前状态条件性地启用/禁用手势
- 根据触摸位置决定是否激活手势
- 根据其他UI状态控制手势
示例:
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == myPanGesture {
// 只在特定条件下允许拖拽
return isEditingMode && !isLocked
}
if gestureRecognizer == pinchGesture {
// 根据内容类型决定是否允许缩放
return contentType == .image
}
return true
}
2. gestureRecognizer(_:shouldReceive:) - Touch
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
作用: 决定手势识别器是否应该接收特定的触摸事件
参数:
gestureRecognizer: 请求接收触摸的手势识别器touch: 待处理的触摸对象
返回值:
true: 允许手势接收这个触摸(默认值)false: 拒绝接收这个触摸
调用时机: 在每个触摸开始时调用
使用场景:
- 根据触摸位置过滤手势
- 根据触摸的view类型决定是否响应
- 实现复杂的触摸区域控制
- 排除特定子视图的触摸
示例:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let point = touch.location(in: self)
if gestureRecognizer == panGesture {
// 只在边缘区域接收拖拽触摸
let edgeWidth: CGFloat = 20
return point.x < edgeWidth || point.x > bounds.width - edgeWidth
}
// 排除按钮区域的触摸
if let touchView = touch.view, touchView is UIButton {
return false
}
return true
}
3. gestureRecognizer(_:shouldReceive:) - Press
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive press: UIPress) -> Bool
作用: 决定手势识别器是否应该接收按压事件(主要用于 Apple TV 遥控器)
参数:
gestureRecognizer: 请求接收按压的手势识别器press: UIPress 对象,包含按压信息
返回值:
true: 允许接收按压事件false: 拒绝接收按压事件
调用时机: 在按压事件开始时调用
使用场景: 主要用于 tvOS 应用,过滤特定的按键事件
4. gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
作用: 决定两个手势识别器是否可以同时识别
参数:
gestureRecognizer: 当前手势识别器otherGestureRecognizer: 另一个手势识别器
返回值:
true: 允许同时识别false: 不允许同时识别(默认值)
调用时机: 当两个手势可能冲突时调用
使用场景:
- 实现复合手势(如同时缩放和旋转)
- 控制手势冲突
- 创建自定义手势组合
- 保护自己的触摸事件不被阻断
示例:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 允许缩放和旋转同时进行
if (gestureRecognizer is UIPinchGestureRecognizer && otherGestureRecognizer is UIRotationGestureRecognizer) ||
(gestureRecognizer is UIRotationGestureRecognizer && otherGestureRecognizer is UIPinchGestureRecognizer) {
return true
}
// 自己的阻断手势不与父容器手势同时识别
if gestureRecognizer == blockingGesture {
if let scrollView = otherGestureRecognizer.view as? UIScrollView {
return false // 阻断父容器滚动
}
return true // 允许其他手势
}
return false
}
5. gestureRecognizer(_:shouldRequireFailureOf:)
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool
作用: 决定当前手势是否应该等待另一个手势失败后才能识别
参数:
gestureRecognizer: 当前手势识别器otherGestureRecognizer: 需要等待失败的手势识别器
返回值:
true: 当前手势等待其他手势失败false: 不等待(默认值)
调用时机: 在手势识别过程中,当需要确定优先级时调用
使用场景:
- 实现手势优先级
- 处理单击/双击冲突
- 创建复杂的手势序列
示例:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 单击手势等待双击手势失败
if gestureRecognizer == singleTapGesture && otherGestureRecognizer == doubleTapGesture {
return true
}
// 拖拽手势等待长按手势失败
if gestureRecognizer == panGesture && otherGestureRecognizer == longPressGesture {
return true
}
return false
}
6. gestureRecognizer(_:shouldBeRequiredToFailBy:)
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool
作用: 决定其他手势是否应该等待当前手势失败后才能识别
参数:
gestureRecognizer: 当前手势识别器otherGestureRecognizer: 可能需要等待的手势识别器
返回值:
true: 其他手势等待当前手势失败false: 不要求等待(默认值)
调用时机: 在手势识别过程中,当需要确定优先级时调用
使用场景:
- 设置手势优先级
- 阻止父容器手势干扰
- 实现自定义手势层级
- 让当前控件的手势优先于父容器
示例:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 要求父容器的滚动手势等待当前手势失败
if gestureRecognizer == myCustomGesture {
if let scrollView = otherGestureRecognizer.view as? UIScrollView {
return isParentScrollView(scrollView)
}
}
// 双击手势要求其他手势等待
if gestureRecognizer == doubleTapGesture {
return otherGestureRecognizer == singleTapGesture
}
return false
}
private func isParentScrollView(_ scrollView: UIScrollView) -> Bool {
var parent = superview
while parent != nil {
if parent === scrollView {
return true
}
parent = parent?.superview
}
return false
}
手势识别器相关属性
重要属性
// 是否取消触摸事件传递
gestureRecognizer.cancelsTouchesInView = false
// 是否延迟触摸开始
gestureRecognizer.delaysTouchesBegan = false
// 是否延迟触摸结束
gestureRecognizer.delaysTouchesEnded = false
// 手势名称(调试用)
gestureRecognizer.name = "MyCustomGesture"
// 手势状态
gestureRecognizer.state // .possible, .began, .changed, .ended, .cancelled, .failed
// 是否启用
gestureRecognizer.isEnabled = true
实际应用示例
完整的自定义滑块示例
class CustomSlider: UISlider {
private var blockingGesture: UIPanGestureRecognizer!
override func awakeFromNib() {
super.awakeFromNib()
setupGestures()
}
private func setupGestures() {
// 创建阻断手势,防止父容器滚动
blockingGesture = UIPanGestureRecognizer(target: self, action: #selector(handleBlockingGesture(_:)))
blockingGesture.delegate = self
blockingGesture.cancelsTouchesInView = false // 关键:不取消自己的触摸事件
blockingGesture.name = "SliderBlockingGesture"
addGestureRecognizer(blockingGesture)
}
@objc private func handleBlockingGesture(_ gesture: UIPanGestureRecognizer) {
// 空实现,仅用于阻断父容器手势
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("✅ CustomSlider touchesBegan")
sendActions(for: .touchDown)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("✅ CustomSlider touchesMoved")
sendActions(for: .valueChanged)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("✅ CustomSlider touchesEnded")
sendActions(for: .touchUpInside)
}
}
extension CustomSlider: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
// 阻断手势只在自己的范围内生效
if gestureRecognizer == blockingGesture {
let point = touch.location(in: self)
return bounds.contains(point)
}
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == blockingGesture {
// 检查是否是父容器的滚动手势
if let scrollView = otherGestureRecognizer.view as? UIScrollView {
if isParentScrollView(scrollView) {
print("❌ 阻断父容器滚动手势")
return false
}
}
// 允许其他手势同时识别
print("✅ 允许其他手势同时识别")
return true
}
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == blockingGesture {
// 要求父容器手势等待阻断手势失败
if let scrollView = otherGestureRecognizer.view as? UIScrollView {
if isParentScrollView(scrollView) {
print("✅ 要求父容器手势等待")
return true
}
}
}
return false
}
private func isParentScrollView(_ scrollView: UIScrollView) -> Bool {
var parent = superview
while parent != nil {
if parent === scrollView {
return true
}
parent = parent?.superview
}
return false
}
}
复杂手势控制示例
class InteractiveImageView: UIImageView {
private var panGesture: UIPanGestureRecognizer!
private var pinchGesture: UIPinchGestureRecognizer!
private var rotationGesture: UIRotationGestureRecognizer!
private var singleTapGesture: UITapGestureRecognizer!
private var doubleTapGesture: UITapGestureRecognizer!
private var isEditMode = false
override func awakeFromNib() {
super.awakeFromNib()
setupGestures()
}
private func setupGestures() {
isUserInteractionEnabled = true
// 拖拽手势
panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGesture.delegate = self
panGesture.name = "ImagePanGesture"
addGestureRecognizer(panGesture)
// 缩放手势
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))
pinchGesture.delegate = self
pinchGesture.name = "ImagePinchGesture"
addGestureRecognizer(pinchGesture)
// 旋转手势
rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:)))
rotationGesture.delegate = self
rotationGesture.name = "ImageRotationGesture"
addGestureRecognizer(rotationGesture)
// 单击手势
singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap(_:)))
singleTapGesture.delegate = self
singleTapGesture.name = "ImageSingleTapGesture"
addGestureRecognizer(singleTapGesture)
// 双击手势
doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
doubleTapGesture.numberOfTapsRequired = 2
doubleTapGesture.delegate = self
doubleTapGesture.name = "ImageDoubleTapGesture"
addGestureRecognizer(doubleTapGesture)
// 设置单击等待双击失败
singleTapGesture.require(toFail: doubleTapGesture)
}
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) { /* 实现拖拽 */ }
@objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) { /* 实现缩放 */ }
@objc private func handleRotation(_ gesture: UIRotationGestureRecognizer) { /* 实现旋转 */ }
@objc private func handleSingleTap(_ gesture: UITapGestureRecognizer) { /* 单击处理 */ }
@objc private func handleDoubleTap(_ gesture: UITapGestureRecognizer) { /* 双击处理 */ }
}
extension InteractiveImageView: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// 编辑模式下才允许变换手势
if gestureRecognizer == panGesture ||
gestureRecognizer == pinchGesture ||
gestureRecognizer == rotationGesture {
return isEditMode
}
// 点击手势总是允许
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
// 所有手势都接收触摸
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 允许缩放、旋转、拖拽同时进行
let transformGestures: [UIGestureRecognizer] = [panGesture, pinchGesture, rotationGesture]
if transformGestures.contains(gestureRecognizer) && transformGestures.contains(otherGestureRecognizer) {
return true
}
// 点击手势不与其他手势同时进行
if gestureRecognizer == singleTapGesture || gestureRecognizer == doubleTapGesture {
return false
}
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 单击等待双击失败
if gestureRecognizer == singleTapGesture && otherGestureRecognizer == doubleTapGesture {
return true
}
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// 双击优先于单击
if gestureRecognizer == doubleTapGesture && otherGestureRecognizer == singleTapGesture {
return true
}
// 阻止父容器滚动
if transformGestures.contains(gestureRecognizer) {
if let scrollView = otherGestureRecognizer.view as? UIScrollView {
return isParentScrollView(scrollView)
}
}
return false
}
private var transformGestures: [UIGestureRecognizer] {
return [panGesture, pinchGesture, rotationGesture]
}
private func isParentScrollView(_ scrollView: UIScrollView) -> Bool {
var parent = superview
while parent != nil {
if parent === scrollView { return true }
parent = parent?.superview
}
return false
}
}
最佳实践
1. 性能优化
- 选择性实现: 只实现需要的代理方法,避免不必要的方法调用
- 轻量级逻辑: 代理方法中避免复杂计算,保持逻辑简单快速
- 缓存结果: 对于重复计算的结果(如父视图查找)可以适当缓存
2. 手势冲突解决
- 明确优先级: 使用
require(toFail:)或代理方法明确手势优先级 - 层级管理: 通过
shouldBeRequiredToFailBy管理父子容器的手势关系 - 精确控制: 使用
shouldReceive精确控制手势的触发区域
3. 调试技巧
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
print("🤝 Simultaneous check:")
print(" Current: \(gestureRecognizer.name ?? "Unknown") (\(type(of: gestureRecognizer)))")
print(" Other: \(otherGestureRecognizer.name ?? "Unknown") (\(type(of: otherGestureRecognizer)))")
print(" Other's view: \(type(of: otherGestureRecognizer.view))")
// 你的逻辑...
let result = true
print(" Result: \(result)")
return result
}
4. 常见问题解决
问题1:触摸事件被手势拦截
解决方案:
gesture.cancelsTouchesInView = false
gesture.delaysTouchesBegan = false
问题2:父容器滚动干扰
解决方案:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == myGesture {
return otherGestureRecognizer.view is UIScrollView
}
return false
}
问题3:手势识别不准确
解决方案:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let point = touch.location(in: self)
return activeArea.contains(point)
}
5. 设计模式
手势管理器模式
class GestureManager: NSObject {
weak var targetView: UIView?
private var gestures: [String: UIGestureRecognizer] = [:]
func setupGestures(for view: UIView) {
targetView = view
// 统一管理所有手势
}
}
extension GestureManager: UIGestureRecognizerDelegate {
// 统一的手势控制逻辑
}
状态机模式
enum GestureState {
case idle, editing, transforming, selecting
}
class StatefulGestureView: UIView {
private var currentState: GestureState = .idle
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return isGestureAllowed(gestureRecognizer, in: currentState)
}
private func isGestureAllowed(_ gesture: UIGestureRecognizer, in state: GestureState) -> Bool {
// 根据状态决定手势是否允许
}
}
总结
UIGestureRecognizerDelegate 是 iOS 手势系统的核心控制接口,掌握这些方法的使用可以:
- 精确控制手势行为 - 通过6个核心方法实现复杂的手势逻辑
- 解决手势冲突 - 处理父子容器、同级视图之间的手势竞争
- 优化用户体验 - 创建流畅、直观的交互效果
- 提高代码质量 - 通过合理的手势管理提升应用的健壮性
关键在于理解每个方法的调用时机和作用范围,根据具体需求选择合适的控制策略,并通过良好的代码组织和调试手段确保手势系统的稳定性。
Giscus 未启用:请在
src/site.config.ts中配置 repoId 与 categoryId。