iOS Accessibility in SwiftUI: Create Accessible Charts using Audio Graphs
In this iOS accessibility tutorial, learn how to make charts in your app more accessible by using Audio Graphs. By David Piper.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
iOS Accessibility in SwiftUI: Create Accessible Charts using Audio Graphs
25 mins
Categorical vs. Numerical Axes
Next, tell the system about the axes of the chart. You can use two types of axes: a numerical axis and a categorical.
A numerical axis uses numbers. This includes every data point that represents a number, e.g., temperature or height of a mountain. Such an axis contains a range of numbers.
A categorical axis shows data divided into groups or categories. It can include names of months, animals or blood types. You define these categories as an array of strings.
For this chart, you'll use both: The x-axis shows month names and thus is a categorical one and the y-axis shows the amount of precipitation as a numerical axis:
In the next section, you'll see how to create them.
Describing the Axes
It's time to add the axes. Start by creating the x-axis. Add the method below makeChartDescriptor
:
private func makeXAxisDescriptor() -> AXCategoricalDataAxisDescriptor {
AXCategoricalDataAxisDescriptor(
title: "Months",
categoryOrder: (0..<12).map(\.monthName)
)
}
An AXCategoricalDataAxisDescriptor
has a title and an ordered list of categories. Here, you are using "Months"
as the title and the months' names as the categories.
monthName
provides the month name for a given number. You can check the implementation of this in Extensions.swift. By mapping the range of (0..<12)
, you are creating a list of all month names.
Next, define the y-axis by adding the code below makeXAxisDescriptor
:
private func makeYAxisDescriptor() -> AXNumericDataAxisDescriptor {
// 1
let maxPrecipitation = (0..<12)
.map { sumPrecipitation($0).value }
.max() ?? 0.0
// 2
return AXNumericDataAxisDescriptor(
title: "Precipitation per month",
range: (0.0 ... maxPrecipitation),
gridlinePositions: []) {
// 3
"Precipitation " +
Measurement<UnitLength>(
value: $0,
unit: .inches
).formatted(.measurement(width: .wide))
}
}
An AXNumericDataAxisDescriptor
represents a numerical axis.
Here’s how this code works:
- First, you get the maximum precipitation in a month. You'll need this value in the next step because a numerical axis needs a range. The minimum value of this range is 0, meaning no precipitation, and
maxPrecipitation
is the maximum value of the range. - In the next step, you create the
AXNumericDataAxisDescriptor
. Pass in a title for the axis and the range explained above. - An axis also needs a way to transform a value to a text read by VoiceOver. Precipitations are
Measurement
values with unitsUnitLength.inches
. Because of that, you'll wrap the value back in aMeasurement
again and useformatted
to create a localized text.
You are ready to use the axes. The only element missing is the data the chart contains.
Describing the Data Points
To create an audio representation of the chart, iOS needs to know which data points it shows. That's the next step. Add the method below makeYAxisDescriptor
:
private func makeDataSeriesDescriptor() -> [AXDataSeriesDescriptor] {
// 1
let dataPoints = (0..<12).map { monthIndex -> AXDataPoint in
let xValue = monthIndex.monthName
let yValue = sumPrecipitation(monthIndex).value
return AXDataPoint(x: xValue, y: yValue)
}
// 2
return [
AXDataSeriesDescriptor(
name: "Precipitation",
// 3
isContinuous: false,
// 4
dataPoints: dataPoints
)
]
}
Lets walk through the code step-by-step:
- An
AXDataSeriesDescriptor
encapsulates an array ofAXDataPoint
. Each of those points has an x and y value. You create them by mapping over the range0..<12
to group the measurements by month. As you've seen when defining the x-axis inmakeXAxisDescriptor
, it's a categorical axis showing the month names. UsemonthName
for the currentmonthIndex
. The y-axis is a numerical one, which is why the y value must be a number. Here, it's the sum of all precipitations in the given month. You'll combine both to anAXDataPoint
for each month. - Now, create the
AXDataSeriesDescriptor
that bundles the data series and gives it a descriptive name. - Notice that the initializer has a property called
isContinuous
, which determines how the data points are presented. As discussed above, the precipitation chart shows noncontinuous data where the bars represent the months. Thus, setisContinuous
tofalse
. When creating the Audio Graph for the temperature chart in a later section, you'll see how to use this value to create line charts. - Finally, pass in the data points created earlier.
Build the app — everything is compiling again. But a final step remains before you can see the results of your work. PrecipitationChart
needs to know about the chart descriptor. Add these two modifiers to HStack
in its body
property:
.accessibilityChartDescriptor(self)
.accessibilityElement(children: .combine)
The first one sets PrecipitationChart
as its own chart descriptor. The second modifier groups all elements of the chart to one item visible to VoiceOver.
Build and run the app. If it isn't already on, activate VoiceOver with a triple press on the side button.
Focus the first weather station and double-tap the screen to select it. Swipe right until VoiceOver highlights the tab Precipitation. Double-tap the screen again to navigate to that tab. Swipe left until you've reached the chart.
Change your rotor setting to Audio Graph by placing two fingers on the screen and rotating them. Repeat this gesture until you set the rotor to Audio Graph.
Next, swipe down until you hear Chart details and select that option by double-tapping the screen. This opens the Audio Graph details view:
When the screen opens, VoiceOver focuses the title at the top of the page. Swipe right until Play is focused. Start playing the Audio Graph by double-tapping.
A line will appear on the left of the chart above the button and start moving left to right. Every time it touches a bar, you'll hear a sound. The pitch represents the data point, higher pitches meaning higher values and lower pitches mean lower values.
There are more details about the data. Swipe right and explore the next sections. Summary contains the text you've set in makeChartDescriptor
. The next section is Features. It shows information about the data points, e.g., about the trend. The last section is Statistics, listing the minimum, maximum and mean values.
Isn't it amazing? Visually impaired users can listen to your chart's data. Audio Graphs makes it easier to understand the data by providing additional information. This is a major improvement in usability!