The onScrollPhaseChange view modifier reports what state a ScrollView is in: idle, touched,
dragged, decelerating, or animating. Use it to react to scrolling itself, such as pausing
expensive work while content is in motion.
The Phases
ScrollPhase is a frozen enum with five cases:
idle: nothing is happening.tracking: the user touches the content but has not dragged yet.interacting: the user drags to start or continue scrolling.decelerating: the finger lifted and the view coasts to its destination.animating: code scrolls the view, throughScrollPositionorScrollViewReader.
Observing Changes
The action closure receives the old and new phase on every transition.
ScrollView {
ForEach(1..<100, id: \.self) { item in
Text(verbatim: item.formatted())
}
}
.onScrollPhaseChange { oldPhase, newPhase in
guard oldPhase != newPhase else { return }
print(newPhase)
}
A second overload adds a ScrollPhaseChangeContext, which carries the scroll velocity and the
current ScrollGeometry.
.onScrollPhaseChange { oldPhase, newPhase, context in
print(context.geometry.visibleRect)
}
The Common Case: isScrolling
Most callers only need "moving or not". ScrollPhase.isScrolling is true for every phase except
idle. Here it redacts rows while the view scrolls.
@State private var isScrolling = false
ScrollView {
ForEach(1..<100, id: \.self) { item in
Text(verbatim: item.formatted())
}
.redacted(reason: isScrolling ? .placeholder : [])
}
.onScrollPhaseChange { oldPhase, newPhase in
isScrolling = newPhase.isScrolling
}