The scrollPosition modifier, new in iOS 17, controls where a ScrollView starts and which
item is visible, without ScrollViewReader. A companion, scrollIndicatorsFlash, blinks the
indicator on demand.
Initial Anchor
scrollPosition(initialAnchor:) sets where the content sits on first layout. The anchor is a
UnitPoint: use a preset such as .center or .bottom, or build one from normalized 0 to 1
coordinates.
ScrollView {
LazyVStack { /* ... */ }
}
.scrollPosition(initialAnchor: .init(x: 0, y: 0.9))
Reading and Writing the Position
The id: variant binds a hashable value. Scrolling writes the first visible view's identity
into the binding; setting the binding scrolls to that view.
struct ContentView: View {
@State private var position: Int?
var body: some View {
ScrollView {
LazyVStack {
Button("Jump...") { position = 99 }
ForEach(0..<100) { index in
Rectangle()
.fill(Color.green.gradient)
.frame(height: 300)
.id(index)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $position)
}
}
Two requirements, both easy to miss:
- The content needs identities.
ForEachover identifiable data provides them; otherwise set.id(_:)explicitly. - The container needs
scrollTargetLayout(orscrollTarget), or theScrollViewcannot find the identifiers and the binding never updates.
Flashing the Indicator
scrollIndicatorsFlash(trigger:) blinks the scroll indicator whenever the passed equatable
value changes, for example after appending content, so the user sees there is more to scroll.
.scrollIndicatorsFlash(trigger: trigger)