How it works
A SequenceGesture combines two gestures so the second can only begin after the first successfully completes. It models interactions that have distinct phases — confirm with one gesture, then act with another — guaranteeing the order in which they recognize. Reach for it when a single tap or drag is too easy to trigger by accident and you want a deliberate hand-off, such as requiring a press-and-hold before a drag takes effect. You rarely construct one directly; instead you compose it through the sequenced(before:) modifier on a gesture.
Order two gestures with sequenced(before:)
Calling sequenced(before:) on a gesture produces a SequenceGesture in which the receiver runs first and the argument runs second. Here
LongPressGesture(minimumDuration: 0.3)is the gate and.sequenced(before: DragGesture())makes the drag wait until the long press has succeeded, so a stray drag does nothing until the hold completes.Read the active phase from the Value enum
A SequenceGesture reports its state as a two-case Value: .first while the leading gesture is active and .second once it has finished and the trailing gesture takes over. The .second case carries the first gesture's value and an optional value from the second, which the example unwraps with
if case .second(true, let drag?) = valueto confirm the press ended and to bind the liveDragGesture.Value.Respond to updates with onChanged
Attach onChanged to react each time the composite value changes during recognition. In the example it fires throughout the drag phase and reads
drag.translationto driveoffset, moving the circle only after the second gesture has become active.Reset on completion with onEnded
onEnded runs when the whole sequence finishes or is cancelled, making it the place to undo transient state. The example clears
isPressedand returnsoffsetto .zero there, while a separate onEnded on the LongPressGesture flipsisPressedtrue the moment the hold succeeds.Apply it to a view with gesture(_:)
Because SequenceGesture conforms to Gesture, you attach it like any other with the gesture(_:) modifier. The fully composed value is stored as
sequencedand applied to theCirclevia.gesture(sequenced), which is where the symbol plugs into the view hierarchy.
LongPressGesture(minimumDuration: 0.3) to something like 1.5 and notice the drag refuses to move the circle until you have held it for that much longer, proving the second gesture is gated on the first.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 SequenceGestureDemo: View {
@State private var isPressed = false
@State private var offset = CGSize.zero
var body: some View {
let sequenced = LongPressGesture(minimumDuration: 0.3)
.onEnded { _ in isPressed = true }
.sequenced(before: DragGesture())
.onChanged { value in
if case .second(true, let drag?) = value {
offset = drag.translation
}
}
.onEnded { _ in
isPressed = false
offset = .zero
}
return VStack(spacing: 20) {
Text("Long-press, then drag")
.font(.headline)
Circle()
.fill(isPressed ? Color.green : Color.blue)
.frame(width: 100, height: 100)
.offset(offset)
.gesture(sequenced)
Text(isPressed ? "Dragging enabled" : "Press to begin")
.foregroundStyle(.secondary)
}
.padding()
}
}