核心摘要:你是否遇到过拖动视图的过程中,如果突然下拉通知中心,视图保持在拖动的中间位置无法恢复,即使添加了 onEnded 复位逻辑?这是因为系统中断了手势,导致 onEnded 未被调用。@GestureState 正是为解决此类瞬态状态与中断复位问题而生的。
@GestureState 专为手势操作设计,能在手势中断时自动重置状态,适合短暂的、与手势相关的状态管理;而 @State 虽然更通用,但在处理手势非正常结束(Cancellation)时往往需要复杂的额外逻辑。
背景
@GestureState 是 SwiftUI 为手势操作设计的专属状态管理工具。在许多场景下,开发者可能发现使用 @State 也能完成类似的任务。那么苹果为何额外提供 @GestureState?其核心价值在于对 非正常结束(中断) 的自动化处理能力。
基础用法对比
@GestureState 和 @State 都可以用于存储和更新状态,例如实现点击或拖拽等交互效果。
使用 @GestureState
以下代码展示了 @GestureState 的标准用法,配合 .updating 修改器:
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 实现,但代码量稍多:
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(如果自定义手势支持) 来手动复位。
总结:如何选择
- 瞬态数据首选 @GestureState:对于拖拽位移 (
Translation)、按压状态 (Pressing) 等只在手势过程中有效的数据,永远首选@GestureState。 - 持久数据使用 @State:如果手势结束后需要保存最终位置(如拖拽卡片停留在新位置),则需使用
@State,但需注意处理中断情况。