Reading time: 1 min
This quick recipe shows how to scroll a SwiftUI ScrollView programatically when the scrolling trigger is not wrapped in a ScrollViewReader. E.g, the image below shows scrolling triggered from navigation bar buttons.
As you know, you can scroll a ScrollView
programatically using a ScrollViewReader
and its ScrollViewProxy
. Triggering a scroll (say, on a button click) from view within the ScrollViewReader is easy, but what if you want it to happen outside of it?
The trick is to:
- Extract the scrolling target (the ID of the view in the
ScrollView
that you wish to scroll to) in an optional@State
var. - Trigger the scroll by setting its value.
- Register its value change with
onChange(of:)
modifier, attached to the scroll view content (which has access to aScrollViewProxy
).
Here's the full code:
struct ScrollFromOutside: View {
private let items = Array(1...100)
// if not nil, determines what the proxy should scroll to
@State private var scrollTarget: Int?
var body: some View {
NavigationView {
ScrollView {
ScrollViewReader { proxy in
LazyVStack {
ForEach(items, id: \.self) { item in
Text("Row \(item)")
.id(item)
}
}
// registers the change of scroll target and triggers the scroll
.onChange(of: scrollTarget) { newValue in
if let target = newValue {
scrollTarget = nil // allows for future triggers
withAnimation {
proxy.scrollTo(target)
}
}
}
}
}
.navigationBarTitle("Scroll from outside", displayMode: .inline)
.navigationBarItems(leading: Button("To top") {
scrollTarget = items.first // triggers scroll
}, trailing: Button("To bottom") {
scrollTarget = items.last // triggers scroll
})
}
}
}