I have two Tables in an HStack. Also in this HStack is a view with some buttons that will programmatically scroll each table when clicked.
Here is a toy example with the tables displaying some numbers.
HStack {
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
VStack {
Button("Scroll First Table") {
// this should scroll the first table to id 99
}
Button("Scroll Second Table") {
// this should scroll the second table to id 99
}
}
}
struct IdentifiableInt: Identifiable {
let id: Int
}
To programmatically scroll the tables, I need two ScrollViewReaders. If I wrap each table with their own ScrollViewReader, I can no longer access the ScrollViewProxy in the buttons' action closures.
HStack {
ScrollViewReader { proxy1 in
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
}
ScrollViewReader { proxy2 in
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
}
VStack {
Button("Scroll First Table") {
proxy1.scrollTo(99) // 'proxy1' is out of scope here :(
}
Button("Scroll Second Table") {
proxy2.scrollTo(99) // 'proxy2' is out of scope here :(
}
}
}
If I wrap both tables in one ScrollViewReader, then both buttons end up scrolling the first table only.
ScrollViewReader { proxy in
HStack {
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
VStack {
Button("Scroll First Table") {
proxy.scrollTo(99) // scrolls the first table
}
Button("Scroll Second Table") {
proxy.scrollTo(99) // also scrolls the first table :(
}
}
}
}
I have also tried scrolling with ScrollPosition and .scrollPosition. While this does work if I were scrolling a ScrollView, it does not scroll the Tables at all.
How can I access the ScrollViewProxys for each table in the VStack?
I don't have a minimum deployment target. Solutions can require any macOS version.
Answer
Creating extra @States to store the ScrollViewProxys and assigning them in onAppear seems to work, but the @States need to be optional, which in my opinion is a bit ugly.
@State private var proxy1: ScrollViewProxy?
@State private var proxy2: ScrollViewProxy?
var body: some View {
HStack {
ScrollViewReader { proxy in
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
.onAppear {
proxy1 = proxy
}
}
ScrollViewReader { proxy in
Table((0..<100).map(IdentifiableInt.init)) {
TableColumn("", value: \.id.description)
}
.onAppear {
proxy2 = proxy
}
}
VStack {
Button("Scroll First Table") {
proxy1?.scrollTo(99)
}
Button("Scroll Second Table") {
proxy2?.scrollTo(99)
}
}
}
}


