这种俗气的解决方法似乎可以解决问题。我将图表放在 ZStack 中,并用 HStack 覆盖图表。 HStack 包含一堆符合 Identifiable 协议的不可见对象。不可见对象的数量、ID 和位置与图表数据匹配。
由于 ZStack 视图现在包含可识别的元素,因此 ScrollViewReader 可以按预期工作。
import SwiftUI
import Charts
struct ChartData: Identifiable {
var day: Int
var value: Int
var id: String { "(day)" }
}
struct ContentView: View {
@State var chartData = [ChartData]()
@State var scrollSpot = ""
let items = 200
let itemWidth: CGFloat = 30
var body: some View {
VStack {
ScrollViewReader { scrollPosition in
ScrollView(.horizontal) {
// Create a ZStack with an HStack overlaying the chart.
// The HStack consists of invisible items that conform to the
// identifible protocol to provide positions for programmatic
// scrolling to the named location.
ZStack {
// Create an invisible rectangle for each x axis data point
// in the chart.
HStack(spacing: 0) {
ForEach(chartData.indices, id: .self) { i in
Rectangle()
.fill(.clear)
// Setting maxWidth to .infinity here, combined
// with spacing:0 above, makes the rectangles
// expand to fill the frame specified for the
// chart below.
.frame(maxWidth: .infinity, maxHeight: 0)
// Here, set the rectangle's id to match the
// charted data.
.id(chartData[i].id)
}
}
Chart(chartData) {
BarMark(x: .value("Day", $0.day),
y: .value("Amount", $0.value),
width: 20)
}
.frame(width: CGFloat(items) * itemWidth, height: 300)
}
}
.padding()
.onChange(of: scrollSpot, perform: {x in
if (!x.isEmpty) {
scrollPosition.scrollTo(x)
scrollSpot = ""
}
})
}
.onAppear(perform: populateChart)
Button("Scroll") {
if let x = chartData.last?.id {
print("Scrolling to item (x)")
scrollSpot = x
}
}
Spacer()
}
}
func populateChart() {
if !chartData.isEmpty { return }
for i in 0..<items {
chartData.append(ChartData(day: i, value: (i % 10) + 2))
}
}
}
恕我直言,这应该可以在 SwiftUI 框外工作。 Apple 的初始值设定项的 cmets 说它 creates a chart composed of a series of identifiable marks. 所以......如果标记是可识别的,那么期望 ScrollViewReader 与图表的标记一起工作并不是一件容易的事。
但是不!
人们希望这是 Apple 的疏忽,因为框架是新的,他们将在即将发布的版本中公开图表标记的 ID。