Hey Justin,
The trick here is extracting the storage of the target outside of the ScrollView. The following approach is easiest within a single View struct, but it can be extended to work across different View structs by using Bindings.
Start off by defining a store property on your struct that holds an optional reference of the target identifier. I'll use Int
s, but you can use anything that conforms to the Hashable
protocol - Strings, Enum
cases etc...
struct ContentView: View {
@State private var scrollTarget: Int?
}
Then set up the skeleton UI. I'll have my main scrollview take up most of the space, but then I'll have a horizontally scrolling row of buttons along the bottom of the screen that scroll to different children of the ScrollView.
struct ContentView: View {
@State private var scrollTarget: Int?
var body: some View {
NavigationView {
VStack {
ScrollView {
ScrollViewReader { (proxy: ScrollViewProxy) in
VStack {
ForEach(0..<100) { i in
Text("Row \(i)")
.frame(height: 40)
.id(i) // Uniquely identify each row in the scrollview by its index
}
}
.frame(maxWidth: .infinity)
.padding()
// Scroll to the desired row when the @State variable changes
}
}
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<10) { i in
Button("Row \(i * 10)") {
// Store the desired scroll index
}
}
}
.padding(.horizontal)
.padding(.vertical, 4)
}
}
.navigationTitle("Scroll Example")
}
}
}
Last but not least we need to make use of the property we defined earlier, as well as SwiftUI's new onChange(of:perform:)
modifier to scroll to the correct view when the variable changes.
struct ContentView: View {
@State private var scrollTarget: Int?
var body: some View {
NavigationView {
VStack {
ScrollView {
ScrollViewReader { (proxy: ScrollViewProxy) in
VStack {
ForEach(0..<100) { i in
Text("Row \(i)")
.frame(height: 40)
.id(i) // Uniquely identify each row in the scrollview by its index
}
}
}
.frame(maxWidth: .infinity)
.padding()
// Scroll to the desired row when the @State variable changes
.onChange(of: scrollTarget) { target in
if let target = target {
scrollTarget = nil
withAnimation {
proxy.scrollTo(target, anchor: .center)
}
}
}
}
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<10) { i in
Button("Row \(i * 10)") {
// Store the desired scroll index
scrollTarget = i * 10
}
}
}
.padding(.horizontal)
.padding(.vertical, 4)
}
}
.navigationTitle("Scroll Example")
}
}
}
Hope that helps!
--Jakub