TechnologiesSwiftUI

SequenceGesture struct

iOSmacOStvOSwatchOSvisionOSiOS 13.0+✗ blank

A gesture that's a sequence of two gestures.

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.

  1. 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.

  2. 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?) = value to confirm the press ended and to bind the live DragGesture.Value.

  3. 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.translation to drive offset, moving the circle only after the second gesture has become active.

  4. 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 isPressed and returns offset to .zero there, while a separate onEnded on the LongPressGesture flips isPressed true the moment the hold succeeds.

  5. 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 sequenced and applied to the Circle via .gesture(sequenced), which is where the symbol plugs into the view hierarchy.

Try it — Raise the minimumDuration in 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.

SequenceGesture.swift
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()
    }
}
Live preview
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →