iOS 17 gives Swift Charts one-line selection: chartXSelection and chartYSelection bind the
value or range under the user's finger. Before that, selection meant hand-rolling a
chartOverlay with a drag gesture and ChartProxy math.
One-Line Selection
Bind an optional value; the framework writes the selected chart value into it. Render feedback
yourself, for example a RuleMark with an annotation.
Chart(Array(zip(numbers.indices, numbers)), id: \.1) { index, number in
LineMark(
x: .value("Index", index),
y: .value("Number", number)
)
if let selectedIndex {
RuleMark(x: .value("Index", selectedIndex))
.annotation(position: .bottom) {
Text(verbatim: selectedIndex.formatted())
.padding()
.background(.regularMaterial)
}
}
}
.chartXSelection(value: $selectedIndex)
Range Selection
The same modifiers accept a ClosedRange binding. A RectangleMark between the bounds is the
usual highlight.
@State private var selectedRange: ClosedRange<Int>?
Chart { /* marks */
if let selectedRange {
RectangleMark(
xStart: .value("Index", selectedRange.lowerBound),
xEnd: .value("Index", selectedRange.upperBound),
yStart: .value("Number", 0),
yEnd: .value("Number", 10)
)
.foregroundStyle(.blue.opacity(0.03))
}
}
.chartXSelection(range: $selectedRange)
Custom Gesture
chartGesture replaces the built-in gesture. It hands you the ChartProxy, so you can tune the
DragGesture (a minimum distance, for example) and drive selection through
selectXRange(from:to:).
.chartGesture { chart in
DragGesture(minimumDistance: 16)
.onChanged {
chart.selectXRange(from: $0.startLocation.x, to: $0.location.x)
}
.onEnded { _ in selectedRange = nil }
}
On iOS 16, the fallback is chartOverlay: a near-transparent rectangle with a drag gesture,
converting the touch x through chart.value(atX:) after subtracting the plot frame origin.