Reputation: 1014
I’m working on a project that displays the number of steps users have taken in the last 15 days on a chart. I’ve implemented the chart selection behavior introduced in iOS 17, which displays a RuleMark
with an annotation for the selected value. However, when I enable a scrollable x-axis, the annotation becomes invisible. See the attached GIF image.
Chart(stepCountRecords) { record in
BarMark(
x: .value("Date", record.date, unit: .day),
y: .value("Count", record.steps)
)
if let selectedDate {
RuleMark(x: .value("Selected", selectedDate, unit: .day))
.foregroundStyle(Color(.secondarySystemFill).opacity(0.3))
.annotation(position: .top, overflowResolution: .init(x: .fit(to: .chart), y: .disabled)) {
Text(selectedDate.formatted())
.padding()
.background(Color(.secondarySystemFill))
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
}
/*
FIXME: Uncomment the following line to trigger the issue in question.
.chartScrollableAxes(.horizontal)
*/
.chartXSelection(value: $selectedDate)
Is this intentional? Or I’m doing something wrong? It’s worth noting that Apple’s Health app supports scrolling and annotation at the same time.
Here’s a small sample project for your reference.
Upvotes: 0
Views: 128
Reputation: 271410
The problem seems to be that the annotation is shown above the bars, which is clipped by some scroll view that allows scrolling.
You can enable overflowResolution
on the y axis,
.annotation(
position: .top,
overflowResolution: .init(
x: .fit(to: .chart),
y: .fit // <---
)
) { ... }
The annotation will now be placed at the top of the chart, overlaying the bars.
If you don't want the top of the bars to be hidden by the annotation, you can add some top padding instead.
.chartPlotStyle {
$0.padding(.top, 100)
}
Using chartXSelection
with a scrollable chart will often cause the selection gesture to be prioritised over the scrolling gesture. Consider using a SpatialTapGesture
to change the selection,
.chartGesture { chartProxy in
SpatialTapGesture().onEnded { value in
selectedDate = chartProxy.value(atX: value.location.x, as: Date.self)
}
}
Here is a complete example:
let data = (0..<30).map {
Point(x: Date().addingTimeInterval(86400 * Double($0)), y: .random(in: 1...10))
}
struct ContentView: View {
@State var selectedDate: Date?
var body: some View {
Chart(data) { record in
BarMark(
x: .value("Date", record.x, unit: .day),
y: .value("Count", record.y)
)
if let selectedDate {
RuleMark(x: .value("Selected", selectedDate, unit: .day))
.foregroundStyle(Color(.secondarySystemFill).opacity(0.3))
.annotation(position: .top, overflowResolution: .init(x: .fit(to: .chart), y: .disabled)) {
Text(selectedDate.formatted())
.padding()
.background(Color(.secondarySystemFill))
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
}
.chartPlotStyle {
$0.padding(.top, 100)
}
.chartGesture { chartProxy in
SpatialTapGesture().onEnded { value in
selectedDate = chartProxy.value(atX: value.location.x, as: Date.self)
}
}
.chartScrollableAxes(.horizontal)
.padding(.horizontal, 20)
.padding(.vertical, 100)
}
}
struct Point: Identifiable {
let id = UUID()
let x: Date
let y: Int
}
Upvotes: 0