TechnologiesSwiftUI

ScrollTransitionPhase enum

iOSmacOStvOSwatchOSvisionOSiOS 17.0+✓ renders

The phases that a view transitions between when it scrolls among other views.

How it works

ScrollTransitionPhase describes where a view sits in its journey through a scroll container's visible region: still off the leading edge, settled fully into view, or passing off the trailing edge. SwiftUI hands you a value of this type inside a scroll transition so you can vary a view's appearance based on its position rather than animating on a fixed timeline. Reach for it whenever you want scrolling itself to drive an effect — fading, scaling, or offsetting cards as they enter and leave — so the motion stays tied to the gesture instead of running independently of it.

  1. Receive the phase from scrollTransition(_:)

    Attaching scrollTransition to a view installs a transition whose closure SwiftUI re-invokes as the view moves through the scroll viewport. The closure receives the proxied content plus a phase of type ScrollTransitionPhase, as in .scrollTransition { content, phase in ... }. You return a modified content, and the phase tells you which state to render for the current scroll position.

  2. Branch on the resting state with isIdentity

    ScrollTransitionPhase exposes isIdentity, a Boolean that is true only in the .identity phase — when the view is fully scrolled into its resting position. Testing it lets you express "no effect when settled, effect otherwise" without naming every case. The example uses phase.isIdentity ? 1 : 0.3 for opacity and phase.isIdentity ? 1 : 0.85 for scale, so each card is solid and full-size at rest and dims and shrinks as it approaches an edge.

  3. Distinguish the three phases

    The type is an enum with three cases: .topLeading for views above or before the visible region, .identity for views resting inside it, and .bottomTrailing for views below or after it. Switching on the phase instead of only checking isIdentity lets you apply different treatments at each edge — for instance offsetting a card one way as it enters and the other way as it leaves.

  4. Read continuous progress with value

    Beyond the discrete cases, ScrollTransitionPhase provides a value property — a Double that is 0 at the identity phase and moves toward -1 or 1 as the view travels toward an edge. Multiplying your effect by this value gives a smooth, position-driven ramp rather than the hard cutover that isIdentity produces, so a card can fade gradually instead of snapping between 1 and 0.3.

  5. Combine the phase with view effects

    The phase is only useful through the modifiers you apply to content inside the closure. The example drives opacity and scaleEffect from the phase, but any animatable modifier works — blur, rotation3DEffect, or offset. Because SwiftUI evaluates the closure continuously as you scroll, the chosen effects interpolate with the gesture instead of playing on their own clock.

Try it — Replace content.opacity(phase.isIdentity ? 1 : 0.3) with content.opacity(1 + phase.value) to see the continuous value ramp a card smoothly from solid to transparent as it nears either edge instead of snapping between two fixed levels.

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.

ScrollTransitionPhase.swift
struct ScrollTransitionPhaseDemo: View {
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                ForEach(0..<8) { i in
                    Text("Card \(i + 1)")
                        .font(.headline)
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(.blue.opacity(0.2))
                        .cornerRadius(12)
                        .scrollTransition { content, phase in
                            content
                                .opacity(phase.isIdentity ? 1 : 0.3)
                                .scaleEffect(phase.isIdentity ? 1 : 0.85)
                        }
                }
            }
            .padding()
        }
    }
}
Live preview
Card 1 Card 2 Card 3 Card 4 Card 5 Card 6 Card 7 Card 8
Card 1 Card 2 Card 3 Card 4 Card 5 Card 6 Card 7 Card 8
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →