🔋

SwiftUI 手势状态管理:详解 @GestureState 与 @State 的中断处理

(更新于 )

核心摘要:你是否遇到过拖动视图的过程中,如果突然下拉通知中心,视图保持在拖动的中间位置无法恢复,即使添加了 onEnded 复位逻辑?这是因为系统中断了手势,导致 onEnded 未被调用。@GestureState 正是为解决此类瞬态状态与中断复位问题而生的。

@GestureState 专为手势操作设计,能在手势中断时自动重置状态,适合短暂的、与手势相关的状态管理;而 @State 虽然更通用,但在处理手势非正常结束(Cancellation)时往往需要复杂的额外逻辑。

背景

@GestureState 是 SwiftUI 为手势操作设计的专属状态管理工具。在许多场景下,开发者可能发现使用 @State 也能完成类似的任务。那么苹果为何额外提供 @GestureState?其核心价值在于对 非正常结束(中断) 的自动化处理能力。

基础用法对比

@GestureState@State 都可以用于存储和更新状态,例如实现点击或拖拽等交互效果。

使用 @GestureState

以下代码展示了 @GestureState 的标准用法,配合 .updating 修改器:

Swift
struct GestureStateExample: View {
    @GestureState var isPressed = false
    
    var body: some View {
        Rectangle()
            .fill(.orange)
            .frame(width: 200, height: 200)
            .gesture(
                DragGesture(minimumDistance: 0)
                    .updating($isPressed) { _, state, _ in
                        state = true
                    }
            )
            .overlay(
                Text(isPressed ? "Pressing" : "Released")
            )
            .animation(.easeInOut, value: isPressed)
    }
}

在此例中,@GestureState 在手势活动期间被更新为 true,当手势结束或被打断时,状态会自动重置为初始值(false)。

使用 @State

类似功能可以用 @State 实现,但代码量稍多:

Swift
struct StateExample: View {
    @State var isPressed = false
    
    var body: some View {
        Rectangle()
            .fill(.orange)
            .frame(width: 200, height: 200)
            .gesture(
                DragGesture(minimumDistance: 0)
                    .onChanged { _ in
                        isPressed = true
                    }
                    .onEnded { _ in
                        isPressed = false
                    }
            )
            .overlay(
                Text(isPressed ? "Pressing" : "Released")
            )
    }
}

表面上看,这两种方法在正常操作下表现一致。

核心区别:手势中断 (Cancellation)

手势中断的处理@GestureState@State 的关键差异所在,也是导致 Bug 的常见源头。

什么是手势中断?

当手势被系统操作打断时(例如:下拉通知中心、来电全屏覆盖、触发了系统级的 App 切换手势但未完成),手势流会被强制终止。此时:

  • @State 的缺陷:系统的中断不会触发 onEnded 闭包。如果你的重置逻辑写在 onEnded 里,变量 isPressed 将永远停留在 true,导致 UI 卡死。
  • @GestureState 的优势:系统会自动检测中断,并将属性强制重置为初始值,无需开发者编写任何额外代码。

示例对比

以下视频演示了手势中断时的行为差异:

  • @GestureState:状态在中断后自动复位。
  • @State:状态卡在“按下”状态,需手动处理中断逻辑(通常非常繁琐)。

提示:如果确实需要使用 @State 处理手势(例如需要持久化手势结束后的位置),你可能需要监听 ScenePhase 变化或使用 .onCancellation (如果自定义手势支持) 来手动复位。

总结:如何选择

  1. 瞬态数据首选 @GestureState:对于拖拽位移 (Translation)、按压状态 (Pressing) 等只在手势过程中有效的数据,永远首选 @GestureState
  2. 持久数据使用 @State:如果手势结束后需要保存最终位置(如拖拽卡片停留在新位置),则需使用 @State,但需注意处理中断情况。

延伸阅读

相关提示

订阅 Fatbobman 周报

每周精选 Swift 与 SwiftUI 开发技巧,加入众多开发者的行列。

立即订阅