How it works
GestureStateGesture is the gesture type SwiftUI produces when you attach the updating(_:body:) modifier to another gesture. It pairs a base gesture with a @GestureState property, feeding live gesture values into that state while the gesture is active and automatically resetting the state to its initial value the instant the gesture ends. Reach for it whenever you want transient, in-flight feedback — a view that follows your finger and snaps back when you lift it — without writing the bookkeeping to clear that state yourself.
Declare the transient state with @GestureState
A @GestureState property holds a value that lives only for the duration of a gesture; SwiftUI restores it to its declared initial value as soon as the gesture finishes. Here
@GestureState private var dragOffset = CGSize.zerois the storage that GestureStateGesture writes into, andCGSize.zerois the value it springs back to on release.Wrap a base gesture with updating(_:body:)
Calling
.updating(_:body:)on any Gesture returns a GestureStateGesture that decorates the original. The base gesture supplies the raw events while the modifier defines how each event maps into the bound state. In the exampleDragGesture().updating(...)turns a plain drag into a GestureStateGesture drivingdragOffset.Bind the state projection with $
The first argument to updating is a GestureState projected with the $ prefix, which is how GestureStateGesture knows which @GestureState to mutate. Passing
$dragOffsethands the gesture write access to that transient property — and only for the gesture's lifetime.Compute the value inside the body closure
The body closure receives the gesture's current value, an inout reference to the state, and a transaction. Assigning to
stateis the only way the bound property changes. The example's{ value, state, _ in state = value.translation }copies the livevalue.translationintodragOffseton every drag update.Attach it like any Gesture and read the state
GestureStateGesture conforms to Gesture, so it slots straight into
.gesture(...)and the rest of your view reads the bound property normally. Here.offset(dragOffset)moves theCircle()as the value flows in, and because the state auto-resets, the circle returns to center the moment the drag ends.
state = value.translation to state = CGSize(width: value.translation.width, height: 0) and the circle will track horizontally only — still snapping back to zero on release, showing how GestureStateGesture funnels just what you assign into the transient state.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct GestureStateGestureDemo: View {
@GestureState private var dragOffset = CGSize.zero
var body: some View {
VStack(spacing: 16) {
Text("Drag the circle")
.font(.headline)
Circle()
.fill(.blue)
.frame(width: 80, height: 80)
.offset(dragOffset)
.gesture(
DragGesture()
.updating($dragOffset) { value, state, _ in
state = value.translation
}
)
Text("Offset: \(Int(dragOffset.width)), \(Int(dragOffset.height))")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
}
}