How it works
A ScrollViewProxy is a lightweight handle that lets you drive a scroll view's content offset from code rather than waiting on the user to drag. SwiftUI hands you one inside a ScrollViewReader, and through it you ask the enclosing scroll view to bring a particular child into view by its identity. Reach for it whenever an event outside the scroll gesture — a button tap, a search result, a newly appended message — needs to move the viewport to a specific row.
Obtain the proxy from ScrollViewReader
You never construct a
ScrollViewProxyyourself; you receive one as the argument to theScrollViewReadercontent closure. Here it arrives asproxy in, and it stays valid for the lifetime of that reader, so any view inside the closure — including theButtonthat sits above the scroll view — can capture and use it.Call scrollTo(_:anchor:) to move the viewport
The proxy's core member is
scrollTo(_:anchor:), which takes the identity of a target child and scrolls until that child is visible. In the exampleproxy.scrollTo(50, anchor: .top)asks the scroll view to reposition so the view identified by50rests against the top edge.Give the anchor a UnitPoint
The
anchorparameter is aUnitPointdescribing where the target should land within the visible region —.top,.center,.bottom, and so on. Passing.toppins row 50 to the top; omittinganchor(or passingnil) scrolls only far enough to make the target visible without forcing a precise alignment.Identify the scroll targets with id(_:)
scrollTocan only find a child that carries a matching identity, so each scrollable view must be tagged with.id(_:). TheForEachrows attach.id(i), which is what lets the integer50passed toscrollToresolve to the correspondingText("Row \(i)").Animate the jump with withAnimation
Scrolling through the proxy happens immediately by default. Wrapping the call in
withAnimation { proxy.scrollTo(50, anchor: .top) }turns the jump into a smooth scroll, because the offset change becomes part of the surrounding transaction.
proxy.scrollTo(50, anchor: .top) to proxy.scrollTo(50, anchor: .center) and watch row 50 settle in the middle of the viewport instead of at its top edge.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 ScrollViewProxyDemo: View {
var body: some View {
ScrollViewReader { proxy in
VStack(spacing: 12) {
Button("Jump to Row 50") {
withAnimation {
proxy.scrollTo(50, anchor: .top)
}
}
.buttonStyle(.borderedProminent)
ScrollView {
LazyVStack(spacing: 8) {
ForEach(0..<100, id: \.self) { i in
Text("Row \(i)")
.frame(maxWidth: .infinity)
.padding(8)
.background(Color.blue.opacity(0.1))
.id(i)
}
}
}
}
.padding()
}
}
}