Audio Graphs (iOS 15+) let VoiceOver play a chart as sound: high pitches for large values, low
pitches for small ones, reached through the rotor menu. The accessibilityChartDescriptor view
modifier attaches that audio representation to any view that draws data, even an image.
Describe the Chart Data
Conform a type to AXChartDescriptorRepresentable and implement makeChartDescriptor(), which
returns an AXChartDescriptor. Axis descriptors carry the structure: categorical for string
labels, numeric for values.
extension ContentView: AXChartDescriptorRepresentable {
func makeChartDescriptor() -> AXChartDescriptor {
let xAxis = AXCategoricalDataAxisDescriptor(
title: "Labels",
categoryOrder: dataPoints.map(\.label)
)
let min = dataPoints.map(\.value).min() ?? 0.0
let max = dataPoints.map(\.value).max() ?? 0.0
let yAxis = AXNumericDataAxisDescriptor(
title: "Values",
range: min...max,
gridlinePositions: []
) { value in "\(value) points" }
let series = AXDataSeriesDescriptor(
name: "",
isContinuous: false,
dataPoints: dataPoints.map { .init(x: $0.label, y: $0.value) }
)
return AXChartDescriptor(
title: "Chart representing some data",
summary: nil,
xAxis: xAxis,
yAxis: yAxis,
additionalAxes: [],
series: [series]
)
}
}
Two decisions encode the chart type: a bar chart uses AXCategoricalDataAxisDescriptor on X and
isContinuous: false; a line chart uses numeric descriptors on both axes and
isContinuous: true.
Attach It to the View
Collapse the drawn chart into one accessibility element, give it a label, then attach the descriptor.
BarChartView(dataPoints: dataPoints)
.accessibilityElement()
.accessibilityLabel("Chart representing some data")
.accessibilityChartDescriptor(self)
VoiceOver then offers the audio graph in the rotor. The view itself can be anything, a custom
HStack of bars or a rendered image, because the descriptor, not the pixels, carries the data.