I need to create a component which enables the users to select a value in a specific date by horizontally scrolling the SwiftUI charts. The image of the component is below.
My code block is as below:
struct SampleModel: Identifiable {
var id = UUID()
var date: String
var value: Double
var animate: Bool = false
}
struct ContentView: View {
@State private var data = [
SampleModel(date: "26\nFr", value: 9.2),
SampleModel(date: "27\nSa", value: 12.5),
SampleModel(date: "28\nSu", value: 15.0),
SampleModel(date: "29\nMo", value: 20.0),
SampleModel(date: "30\nTu", value: 5.0),
SampleModel(date: "31\nWe", value: 7.0),
SampleModel(date: "01\nTh", value: 3.0),
SampleModel(date: "02\nFr", value: 20.0),
SampleModel(date: "03\nSa", value: 5.0),
SampleModel(date: "04\nSu", value: 7.0),
SampleModel(date: "05\nMo", value: 3.0),
SampleModel(date: "06\nTu", value: 10.0),
SampleModel(date: "07\nWe", value: 21.0),
SampleModel(date: "08\nTh", value: 14.0),
SampleModel(date: "09\nFr", value: 10.0),
SampleModel(date: "10\nSt", value: 7.0),
SampleModel(date: "11\nSu", value: 15.0),
SampleModel(date: "12\nMo", value: 17.0),
SampleModel(date: "13\nTu", value: 29.0)
]
@State private var scrollPosition: String = "26\nFr"
@State var priceText: String = ""
var body: some View {
GeometryReader { geometry in
VStack {
Text(priceText)
.padding()
Chart(data) { flight in
BarMark(x: .value("Date", flight.date),
y: .value("Price", flight.value),
width: 10.0)
.foregroundStyle(
Gradient(
colors: [
.blue,
.green
]
)
)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
.frame(width: .infinity, height: 180)
.background(content: {
VStack {
Color.gray.frame(width: 1)
Color.clear.frame(height: 40)
}
})
.chartXAxis(content: {
AxisMarks(preset: .extended, position: .bottom) { value in
let label = value.as(String.self)!
AxisValueLabel(label)
.foregroundStyle(.gray)
}
})
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 11)
.chartYAxis(.hidden)
.chartScrollTargetBehavior(
.valueAligned(unit: 1)
)
.chartOverlay { proxy in
}
}
.frame(width: .infinity)
}
}
}
I need to observe the content offset of the horizontally scrolling chart in order to retrieve the centre value based on the position by using proxy
of chartOverlay
. Is there a way to observe the content offset of the scrolling chart? Or is there another perspective to retrieve the centre value in the scrolling chart?
Following your approach,
I added some improvements, I added margin on the left and right side, allowing a proper selection when scrolling, also defined a scrollPosition which is Int
to track the index selected of your data.
If you want to change the value shown on top you only need to modify the selectionText
function to return .value
if you need
hope this is what you are asking for.
import SwiftUI
import Charts
struct SampleModel: Identifiable {
var id = UUID()
var date: String
var value: Double
var animate: Bool = false
}
struct ContentView: View {
@State private var data = [
SampleModel(date: "26\nFr", value: 9.2),
SampleModel(date: "27\nSa", value: 12.5),
SampleModel(date: "28\nSu", value: 15.0),
SampleModel(date: "29\nMo", value: 20.0),
SampleModel(date: "30\nTu", value: 5.0),
SampleModel(date: "31\nWe", value: 7.0),
SampleModel(date: "01\nTh", value: 3.0),
SampleModel(date: "02\nFr", value: 20.0),
SampleModel(date: "03\nSa", value: 5.0),
SampleModel(date: "04\nSu", value: 7.0),
SampleModel(date: "05\nMo", value: 3.0),
SampleModel(date: "06\nTu", value: 10.0),
SampleModel(date: "07\nWe", value: 21.0),
SampleModel(date: "08\nTh", value: 14.0),
SampleModel(date: "09\nFr", value: 10.0),
SampleModel(date: "10\nSt", value: 7.0),
SampleModel(date: "11\nSu", value: 15.0),
SampleModel(date: "12\nMo", value: 17.0),
SampleModel(date: "13\nTu", value: 29.0)
]
@State private var scrollPosition: Int = 0
@State var priceText: String? = nil
@State var selectedIndex: Int = 0
var body: some View {
GeometryReader { geometry in
VStack {
if let price = priceText {
Text(price)
.padding()
}
Text(selectionText())
.padding()
Chart(Array(zip(data.indices, data)), id: \.0) { index, flight in
BarMark(x: .value("Index", index),
y: .value("Price", flight.value), width: .fixed(10))
.foregroundStyle(
Gradient(
colors: [
.blue,
.green
]
)
)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
.frame(height: 180)
.background(content: {
VStack {
Color.gray.frame(width: 1)
Color.clear.frame(height: 40)
}
})
.chartXAxis(content: {
AxisMarks(preset: .aligned, position: .bottom, values: .stride(by: 1)) { value in
let label = data[value.index].date
AxisValueLabel(label)
.foregroundStyle(.gray)
}
})
.chartXVisibleDomain(length: 10)
.chartYAxis(.hidden)
.chartScrollTargetBehavior(
.valueAligned(unit: 1)
)
.chartScrollableAxes(.horizontal)
.chartScrollPosition(x: $scrollPosition)
.contentMargins(Edge.Set(arrayLiteral: [.leading, .trailing]), geometry.size.width/2)
}
}
}
private func selectionText() -> String {
guard scrollPosition >= .zero else {
return data[.zero].date
}
guard scrollPosition < data.count else {
return data[data.count - 1].date
}
return data[scrollPosition].date
}
}