Accessibility 进阶 20m
为 App 中的图表添加无障碍支持
Bring accessibility to charts in your app
2021年6月11日
一句话判断
图表是视障用户最大的信息障碍 —— 你不能用一个 accessibilityLabel 说完整个折线图。这集 Session 教你用 Data Sonification(数据声音化)、Audio Graph(音频图表)和分层描述来让图表”可听见”。
这场 Session 讲了什么
Session 专门讲解了如何为图表类可视化内容提供无障碍访问。视觉用户一眼就能看出趋势(上涨、下跌、峰值),但 VoiceOver 用户只能听到一串数字,无法理解数据模式。
苹果提供的解决方案有三个层次:
- 概述(Summary):用自然语言描述图表的整体趋势(“过去 30 天的销售数据,整体呈上升趋势,最高点在 6 月 15 日”)。
- 数据点导航:用自定义 Rotor 让 VoiceOver 用户逐个浏览数据点,每个点包含数值和时间信息。
- Audio Graph(音频图表):iOS 15 新增的
AXChart协议,用音调变化表达数据趋势 —— 高音代表高值、低音代表低值,随时间播放让用户”听到”数据曲线。
值得深挖的点
Audio Graph 的用户体验。当 VoiceOver 用户在一个图表上执行特定手势(两指轻点并拖动)时,系统会播放一段音频:音调从低到高对应数据从低到高。用户可以通过拖动速度控制播放速度,也可以暂停在某个数据点上听到具体的数值。这个功能的底层依赖 AXChartDescriptor —— 你提供数据点数组,系统生成音频。
分层描述的模板。苹果建议图表的无障碍描述分三层:第一层是 Summary(一句话概括趋势)、第二层是 Data Points(可以逐个浏览的数据列表)、第三层是 Audio Graph(声音化表达)。VoiceOver 用户通过 Rotor 在这三层之间切换。
代码片段
创建 Audio Graph 描述:
import Accessibility
// 为图表创建 Audio Graph 描述
func createChartDescriptor(for data: [DataPoint]) -> AXChartDescriptor {
// 创建 X 轴描述
let xAxis = AXNumericDataAxisDescriptor(
title: "日期",
range: (data.first?.date ?? Date())...(data.last?.date ?? Date()),
gridlinePositions: [],
valueDescriptionProvider: { value in
let date = Date(timeIntervalSince1970: value)
return date.formatted(date: .abbreviated, time: .omitted)
}
)
// 创建 Y 轴描述
let yAxis = AXNumericDataAxisDescriptor(
title: "销售额(美元)",
range: 0...(data.map(\.value).max() ?? 100),
gridlinePositions: [],
valueDescriptionProvider: { value in
"$\(Int(value))"
}
)
// 创建数据点序列
let series = AXDataSeriesDescriptor(
name: "日销售额",
isContinuous: true,
dataPoints: data.map { point in
AXDataPoint(
x: point.date.timeIntervalSince1970,
y: point.value,
additionalValues: [],
label: nil
)
}
)
return AXChartDescriptor(
title: "过去 30 天销售额",
summary: "整体呈上升趋势,6月15日达到峰值 $\(data.map(\.value).max() ?? 0)",
xAxis: xAxis,
yAxis: yAxis,
series: [series]
)
}
SwiftUI 中应用图表描述:
import SwiftUI
struct AccessibleChartView: View {
let data: [DataPoint]
var body: some View {
ChartView(data: data)
// 附加图表无障碍描述
.accessibilityChartDescriptor(createChartDescriptor(for: data))
// 概述层
.accessibilityElement(children: .ignore)
.accessibilityLabel(chartSummary)
.accessibilityValue("使用两指轻点并拖动来听取音频图表")
// 自定义 Rotor:按数据点浏览
.accessibilityRotor("数据点") {
ForEach(Array(data.enumerated()), id: \.offset) { index, point in
AccessibilityRotorEntry(
"\(point.date.formatted()): $\(Int(point.value))",
id: index
) {
// 聚焦到具体数据点
}
}
}
}
private var chartSummary: String {
let trend = data.last!.value > data.first!.value ? "上升" : "下降"
let maxVal = data.map(\.value).max()!
let maxDate = data.first { $0.value == maxVal }!.date
return "销售额\(trend)趋势。过去30天最高为\(maxDate.formatted(date: .abbreviated, time: .omitted))的 $\(Int(maxVal))"
}
}
使用 AttributedString 优化数据点描述:
import Foundation
func describeDataPoint(_ point: DataPoint, index: Int, total: Int) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
// 提供上下文的描述
let position: String
if index == 0 {
position = "起始点"
} else if index == total - 1 {
position = "终点"
} else {
position = "第\(index + 1)个点"
}
return "\(position): \(dateFormatter.string(from: point.date)), 销售额 \(point.value, specifier: "%.0f") 美元"
}
最佳实践
- 图表概述要用自然语言描述趋势,不要罗列数字。“销售额本月持续增长,月中达到峰值”比”100, 120, 135, 180, 165, 140”有用得多。
- Audio Graph 是最有力的补充。视障用户反馈说,听到数据曲线比听到任何文字描述都能更快理解趋势。确保你的
AXChartDescriptor数据点足够密集(至少 20 个点)。 - 图表颜色不能是唯一的信息载体。色盲用户看不到红绿对比。用纹理、形状标签或文字标注补充颜色编码。
还有什么值得关注
AXChartDescriptor同时支持折线图、柱状图和散点图,通过isContinuous参数区分。- iOS 15 的 VoiceOver 新增了”音频图表”手势,用户可以在任何支持
AXChart的元素上触发。 - 苹果推荐在图表旁边提供一个”数据表格”视图的切换选项,让用户选择最适合自己的数据消费方式。
WWDC 2021