Gesture Recognizer — 是什么,iOS 中的触摸处理

作者: IT Sectr 发布日期: 2026-02-27 阅读时间: 8 分钟
Gesture Recognizer — UIKit 中的一种抽象,它将用户的触摸序列转换为识别出的手势:点击、滑动、长按、捏合或旋转。无需在 touchesBegan/touchesMoved 方法中手动跟踪坐标,开发者只需将准备好的 UIGestureRecognizer 对象添加到 UIView 并实现 action 方法。根据 Apple Developer Documentation,UIGestureRecognizer 支持 7 个内置子类,覆盖了移动应用中 95% 的典型手势。

要点

  • UIGestureRecognizer — UIKit 的抽象类,用于识别触摸模式:点击、滑动、捏合、旋转和长按。
  • 7 个内置子类:UITapGestureRecognizer、UISwipeGestureRecognizer、UIPinchGestureRecognizer、UIRotationGestureRecognizer、UILongPressGestureRecognizer、UIPanGestureRecognizer、UIScreenEdgePanGestureRecognizer。
  • 每个识别器都有状态(possible、began、changed、ended、cancelled、failed),随着手指移动而更新。
  • 使用 require(toFail:) 方法解决手势之间的冲突,该方法设置识别优先级。
  • Gesture Recognizer 可与 UIKit 和 SwiftUI 中的 UIViewRepresentable 配合使用,但在原生 SwiftUI 中应用手势修饰符。

什么是 Gesture Recognizer?

UIGestureRecognizer — UIKit 中的抽象基类,将手势识别过程分解为独立的对象。每个子类负责一种手势类型:UITapGestureRecognizer — 用于指定数量手指的点击,UISwipeGestureRecognizer — 用于指定方向的滑动。开发者通过 addGestureRecognizer(:) 方法将识别器添加到 UIView,UIKit 自动跟踪触摸、更新状态并在识别时调用操作。

在 UIGestureRecognizer(iOS 3.2,2010)出现之前,开发者重写 UIResponder 方法 — touchesBegan、touchesMoved、touchesEnded — 并手动分析触摸轨迹。这导致代码重复和处理同时触摸时出错。Apple 将此逻辑封装到 UIGestureRecognizer 中,增加了对多点触控、手势取消以及多个识别器在同一视图上同时工作的支持。

根据 WWDC 会议,Gesture Recognizer 在 iPad 上处理多达 11 个同时触摸,在 iPhone 上处理 5 个。在 IT Sectr,我们将 UIGestureRecognizer 作为所有 UIKit 项目中处理用户输入的标准方式 — 这消除了与手动跟踪 touchesBegan 相关的错误。

UIGestureRecognizer 的状态

每个 UIGestureRecognizer 经历 7 种可能的状态,在 UIGestureRecognizer.State 枚举中定义。这些状态反映了识别的生命周期:从检测触摸到手势完成或取消。理解这些状态对于实现自定义识别器和调试冲突至关重要。

状态 含义 何时发生
.possible 初始状态,手势尚未被识别 添加到视图后立即
.began 手势被识别并开始执行 手指第一次移动时(pan/longPress)
.changed 手势参数已更改(坐标、角度) 每次手指移动时
.ended 用户抬起手指,手势完成 在 touchesEnded 时
.cancelled 手势被系统中断(来电、方向改变) 在 touchesCancelled 时
.failed 手势未根据条件被识别 在 touchesCancelled 且未识别时
.recognized .ended 的同义词;手势被成功识别 与 .ended 相同

离散手势(tap、swipe)从 .possible 直接变为 .ended 或 .failed。连续手势(pan、pinch、rotation、longPress)经历 .possible → .began → .changed(多次)→ .ended。在 action 方法中检查 gestureRecognizer.state — 这可以区分手势的开始、变化和结束。

iOS 中的手势类型

UIKit 提供 7 个 UIGestureRecognizer 的内置子类,覆盖大多数交互场景。每个子类都有特定的设置:numberOfTapsRequired 用于点击,direction 用于滑动,minimumPressDuration 用于长按。

  • UITapGestureRecognizer — 识别单击和多次点击。可配置点击次数(numberOfTapsRequired)和手指数量(numberOfTouchesRequired)。用于按钮、链接、元素选择。
  • UISwipeGestureRecognizer — 识别四个方向之一的滑动(right、left、up、down)。direction 属性设置方向;numberOfTouchesRequired — 手指数量。
  • UIPanGestureRecognizer — 用于拖拽的连续手势。translation(in:) 方法返回从起始点的位移。用于拖放、轮播、下拉刷新。
  • UIPinchGestureRecognizer — 双指缩放。scale 属性反映当前缩放系数。在 Pinch-to-Zoom 文章中有详细讨论。
  • UIRotationGestureRecognizer — 双指旋转。rotation 属性以弧度存储角度。用于旋转图像、地图、画布。
  • UILongPressGestureRecognizer — 长按。参数 minimumPressDuration(秒)、allowableMovement(像素)。用于上下文菜单、拖拽元素。
  • UIScreenEdgePanGestureRecognizer — 从屏幕边缘平移。edges 属性指定要跟踪的边缘。用于导航手势、通知面板。

对于自定义手势(例如绘制锯齿形),创建 UIGestureRecognizer 的子类,重写 touchesBegan、touchesMoved、touchesEnded 方法并更新状态。Apple 建议尽可能使用内置类 — 它们经过优化并能正确相互交互。

解决手势之间的冲突

当同一个 UIView 上使用多个 UIGestureRecognizer(例如 tap 和 double-tap)时,会产生识别冲突:双击时,单击首先触发。作为解决方案,Apple 提供了 require(toFail:) 方法,该方法延迟一个手势的识别,直到另一个手势失败。

其机制如下:通过调用 tapRecognizer.require(toFail: doubleTapRecognizer),指定 tapRecogniser 仅在 doubleTapRecogniser 以 .failed 结束后才进入 .recognized 状态。这会在执行单击前增加约 0.3 秒的延迟 — 用户双击,第一次点击被忽略。另一种方法 — UIGestureRecognizerDelegate 代理的 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) 方法,允许同时识别(例如地图上的 pan + pinch)。

根据 WWDC 2020,UIKit 应用中约 15% 的错误与手势冲突的错误配置有关。在 IT Sectr,我们标准化了方法:为每个屏幕创建带有 require(toFail:) 优先级的手势方案 — 这完全消除了双击触发的错误。

Swift 代码示例

示例 1:UITapGestureRecognizer — 点击图片

为 UIImageView 添加单击处理程序。点击时,图像更改透明度 — 最简单的示例,展示手势与视图的绑定。

swift
import UIKit

class ImageViewController: UIViewController {

    @IBOutlet private var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let tap = UITapGestureRecognizer(
            target: self,
            action: #selector(handleTap(_:))
        )
        tap.numberOfTapsRequired = 1
        imageView.addGestureRecognizer(tap)
        imageView.isUserInteractionEnabled = true
    }

    @objc private func handleTap(_: UITapGestureRecognizer) {
        UIView.animate(withDuration: 0.2) {
            self.imageView.alpha = self.imageView.alpha == 1.0 ? 0.5 : 1.0
        }
    }
}

关键点:UIImageView 上的 isUserInteractionEnabled 默认为 false — 没有此标志,Gesture Recognizer 将不会接收触摸。对于 UIView 和 UIButton,该标志默认启用。action 方法接受 UITapGestureRecognizer 参数,通过该参数可以获取 location(in:) 以确定点击坐标。

示例 2:UISwipeGestureRecognizer — 滑动导航

实现向左滑动返回上一个屏幕。演示方向配置和手势与控制器根视图的绑定。

swift
import UIKit

class DetailViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let swipeLeft = UISwipeGestureRecognizer(
            target: self,
            action: #selector(handleSwipe(_:))
        )
        swipeLeft.direction = .left
        view.addGestureRecognizer(swipeLeft)
    }

    @objc private func handleSwipe(_: UISwipeGestureRecognizer) {
        navigationController?.popViewController(animated: true)
    }
}

UISwipeGestureRecognizer — 离散手势:识别后立即进入 .recognised,没有中间的 .changed。因此,在操作中无需检查状态 — 手势要么被识别(调用操作),要么不被识别。direction 属性接受四个值之一:.left、.right、.up、.down。要支持多个方向,请为每个方向创建单独的识别器。

示例 3:使用 require(toFail:) 组合手势

展示如何在同一视图上配置单击和双击而不产生冲突。双击识别器具有优先级 — 仅当双击未被识别时单击才会触发。

swift
import UIKit

class TapViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let singleTap = UITapGestureRecognizer(
            target: self,
            action: #selector(handleSingleTap)
        )
        singleTap.numberOfTapsRequired = 1

        let doubleTap = UITapGestureRecognizer(
            target: self,
            action: #selector(handleDoubleTap)
        )
        doubleTap.numberOfTapsRequired = 2

        singleTap.require(toFail: doubleTap)

        view.addGestureRecognizer(singleTap)
        view.addGestureRecognizer(doubleTap)
    }

    @objc private func handleSingleTap() {
        print("Single tap — after 0.3s delay")
    }

    @objc private func handleDoubleTap() {
        print("Double tap — instant")
    }
}

没有 require(toFail:) 时,双击会先调用 handleSingleTap,然后调用 handleDoubleTap — 这会破坏用户体验。使用 require(toFail:) 后,单击会等待约 0.3 秒以确保第二次点击不会发生。在 IT Sectr,此模式用于图像编辑器和图库中,双击放大,单击选择元素。

常见问题

可以在一个视图上使用多个 Gesture Recognizer 吗?

可以,UIView 同时支持多个 UIGestureRecognizer。要解决冲突,请使用 require(toFail:) 方法,该方法设置识别顺序。要让手势并行工作(例如地图上的 pan + pinch),请实现返回 true 的 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) 代理方法。

UIGestureRecognizer 与 touchesBegan 有何不同?

UIGestureRecognizer — 高级抽象,自动识别触摸模式并管理状态。touchesBegan — UIResponder 的低级方法,需要手动跟踪坐标、时序和触摸取消。Gesture Recognizer 更简单、更可靠,是标准手势的首选;touchesBegan 仅适用于自定义图形。

Gesture Recognizer 在 SwiftUI 中有效吗?

在原生 SwiftUI 中使用修饰符:onTapGesture、onLongPressGesture、DragGesture、MagnificationGesture、RotationGesture。这些是 UIGestureRecognizer 的声明式对应物,集成到 SwiftUI 层级中。如有必要,可以通过 UIViewRepresentable 包装 UIKit 识别器,但 Apple 建议使用原生 SwiftUI 手势。

总结

  • UIGestureRecognizer — UIKit 抽象,将手势识别逻辑分解为独立对象,并自动管理状态。
  • 7 个内置子类覆盖了 95% 的典型手势:点击、滑动、平移、捏合、旋转、长按和边缘滑动。
  • 每个识别器经历 7 种状态 — 从 .possible 到 .ended/.failed,允许精确跟踪离散和连续手势。
  • require(toFail:) 方法通过设置识别优先级来解决同一视图上手势之间的冲突。
  • 在 SwiftUI 中使用声明式手势修饰符,但 UIKit 识别器可通过 UIViewRepresentable 访问。
  • Gesture Recognizer 是 UIKit 项目的必需项;touchesBegan 仅适用于自定义绘图和低级图形。
  • 对单击/双击对使用 require(toFail:),对并行手势使用 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读