TechnologiesSwiftUIScrolling

ScrollPhaseChangeContext struct

iOSmacOStvOSwatchOSvisionOSiOS 18.0+✓ renders

A type that provides you with more content when the phase of a scroll

How it works

ScrollPhaseChangeContext carries the supplementary information SwiftUI hands you at the instant a scroll view moves between phases — for example from interacting to decelerating, or from animating back to idle. A phase transition alone tells you that the scroll state changed, but not the conditions surrounding it; the context fills that gap by exposing the geometry and momentum at the moment of the change. Reach for it inside a phase-change callback whenever your response needs to depend on more than the phase value itself, such as how fast the content was moving or where it had scrolled to.

  1. Receive the context from onScrollPhaseChange(_:)

    The context is delivered as the third parameter of the closure you pass to the scroll view's phase-change modifier. SwiftUI invokes the closure on every transition and supplies the old phase, the new phase, and the surrounding context. In the example, .onScrollPhaseChange { _, newPhase, context in ... } ignores the previous phase, records newPhase, and keeps context available for the rest of the body.

  2. Read momentum with the velocity property

    ScrollPhaseChangeContext exposes the scroll view's velocity at the transition, letting you distinguish a gentle settle from a fast flick when deciding how to react. The example touches context.velocity to show where that signal lives; a real handler might branch on its magnitude to trigger different behavior for slow versus rapid scrolling.

  3. Pair the phase with the context to drive state

    The value is meant to be consumed alongside the new phase rather than in isolation — the phase says what happened, the context says under what conditions. Here the closure writes phase = "\(newPhase)" into @State so the Text("Scroll phase: \(phase)") label updates live, while context stands ready to refine that response when the transition warrants it.

  4. Inspect scroll geometry through the context

    Beyond velocity, the context bundles the scroll geometry in effect at the change, so handlers that need the current offset or content size can read it without separately tracking position. This makes ScrollPhaseChangeContext the single place to look for the full state of the scroll view at a transition, rather than stitching together several observers.

Try it — Inside the .onScrollPhaseChange closure, append the momentum to the label with phase = "\(newPhase) v=\(context.velocity)" and flick the list — the velocity reading spikes during the decelerating phase and falls to zero as it returns to idle.

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.

ScrollPhaseChangeContext.swift
struct ScrollPhaseChangeContextDemo: View {
    @State private var phase = "idle"
    var body: some View {
        VStack(spacing: 12) {
            Text("Scroll phase: \(phase)")
                .font(.headline)
            ScrollView {
                ForEach(0..<20) { i in
                    Text("Row \(i)")
                        .frame(maxWidth: .infinity)
                        .padding(8)
                        .background(.quaternary)
                }
            }
            .onScrollPhaseChange { _, newPhase, context in
                phase = "\(newPhase)"
                _ = context.velocity
            }
        }
        .padding()
    }
}
Live preview
Scroll phase: idle Row 0 Row 1 Row 2 Row 3 Row 4 Row 5 Row 6 Row 7 Row 8 Row 9 Row 10 Row 11 Row 12 Row 13 Row 14 Row 15 Row 16 Row 17 Row 18 Row 19
Scroll phase: idle Row 0 Row 1 Row 2 Row 3 Row 4 Row 5 Row 6 Row 7 Row 8 Row 9 Row 10 Row 11 Row 12 Row 13 Row 14 Row 15 Row 16 Row 17 Row 18 Row 19
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →