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.
Receive the phase from scrollTransition(_:)
Attaching
scrollTransitionto a view installs a transition whose closure SwiftUI re-invokes as the view moves through the scroll viewport. The closure receives the proxiedcontentplus aphaseof typeScrollTransitionPhase, as in.scrollTransition { content, phase in ... }. You return a modifiedcontent, and the phase tells you which state to render for the current scroll position.Branch on the resting state with isIdentity
ScrollTransitionPhaseexposesisIdentity, a Boolean that is true only in the.identityphase — 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 usesphase.isIdentity ? 1 : 0.3for opacity andphase.isIdentity ? 1 : 0.85for scale, so each card is solid and full-size at rest and dims and shrinks as it approaches an edge.Distinguish the three phases
The type is an enum with three cases:
.topLeadingfor views above or before the visible region,.identityfor views resting inside it, and.bottomTrailingfor views below or after it. Switching on the phase instead of only checkingisIdentitylets you apply different treatments at each edge — for instance offsetting a card one way as it enters and the other way as it leaves.Read continuous progress with value
Beyond the discrete cases,
ScrollTransitionPhaseprovides avalueproperty — aDoublethat 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 thatisIdentityproduces, so a card can fade gradually instead of snapping between 1 and 0.3.Combine the phase with view effects
The phase is only useful through the modifiers you apply to
contentinside the closure. The example drivesopacityandscaleEffectfrom the phase, but any animatable modifier works —blur,rotation3DEffect, oroffset. Because SwiftUI evaluates the closure continuously as you scroll, the chosen effects interpolate with the gesture instead of playing on their own clock.
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.
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()
}
}
}