(SwiftUIアクセシビリティ)Audio Graphs入門
はじめに
オーディオグラフ(Audio Graphs)は視覚に障害のあるユーザーを想定したアクセシビリティ機能です。グラフにプロットされた値を音色の高低として再生することで、全体像を伝える機能です。
オーディオグラフはSwiftUIとUIKitどちらでも利用可能です。今回はSwiftUIに限定して説明を進めます。
聞いてみよう
オーディオグラフはVoiceOverスクリーンリーダーを有効にすると利用可能になります。グラフにフォーカスを合わせた後に上下フリックをして「オーディオグラフを再生」と聞こえたらタップします。
さらに、オーディオグラフに準拠した実装をすると、OSが自動的にグラフの詳細画面を生成します。。グラフにフォーカスを合わせた後に上下フリックをして「グラフの詳細」が聞こえたらタップします。
採用例
iOS標準アプリの場合、以下のアプリでオーディオグラフが利用可能です。
- 設定アプリのバッテリー残量グラフ
- ヘルスケアアプリの歩数や体重などの各種グラフ
- 株価アプリの銘柄別グラフ
上記はあくまで一例です。他にもオーディオグラフに対応しているアプリがあるかもしれません。
どのような場面で使うべきか?
オーディオグラフは時系列データを表現するのに最も適しています。
なお、グラフの中にプロットする値の個数は最大で100個ほどに制限する必要があります。詳細は後ほど説明しますが、値の個数が多すぎるとアプリがクラッシュする不具合を確認しています。
バッテリー残量のグラフを例にすると1時間の残量変化をプロットする場合、秒単位の3600個は多すぎるため、分単位で60個にする必要があります。
利用可能なプラットフォーム一覧
オーディオグラフは以下のプラットフォームで利用可能です。
- iOS 15.0+
- iPadOS 15.0+
- Mac Catalyst 15.0+
- macOS 12.0+
- tvOS 15.0+
- visionOS 1.0+
- watchOS 8.0+
実装の手順
オーディオグラフは次の手順で実装します。
- AXChartDescriptorRepresentableプロトコルを満たすモデルを実装する
- 任意のビューに
.accessibilityChartDescriptor()
Modifierを追加する
実装例
以下に実装例を示します。GitHubに公開しているので、そちらも参考にしてください。
github.com/moutend/AudioGraphSampleApp
import SwiftUI
struct DataPoint: Identifiable {
let id = UUID()
let label: String
let value: Double
}
struct BarChartView: View {
let title: String
let xAxisLabel: String
let yAxisLabel: String
let yAxisUnit: String
let dataPoints: [DataPoint]
var body: some View {
VStack {
HStack(alignment: .bottom) {
ForEach(self.dataPoints) { dataPoint in
Rectangle()
.fill(.red)
.frame(width: 20, height: dataPoint.value * 30)
}
}
.accessibilityElement()
.accessibilityLabel("グラフ")
.accessibilityChartDescriptor(self)
Text(self.title)
.font(.title)
.bold()
.padding()
.accessibilityAddTraits(.isHeader)
}
}
}
extension BarChartView: AXChartDescriptorRepresentable {
func makeChartDescriptor() -> AXChartDescriptor {
let xAxis = AXCategoricalDataAxisDescriptor(
title: self.xAxisLabel,
categoryOrder: self.dataPoints.map(\.label)
)
let min = self.dataPoints.map(\.value).min() ?? 0.0
let max = self.dataPoints.map(\.value).max() ?? 0.0
let yAxis = AXNumericDataAxisDescriptor(
title: self.yAxisLabel,
range: min...max,
gridlinePositions: []
) { value in "\(value)\(self.yAxisUnit)" }
let series = AXDataSeriesDescriptor(
name: "",
isContinuous: false,
dataPoints: dataPoints.map {
.init(x: $0.label, y: $0.value)
}
)
return AXChartDescriptor(
title: self.title,
summary: nil,
xAxis: xAxis,
yAxis: yAxis,
additionalAxes: [],
series: [series]
)
}
}
struct ContentView: View {
let dataPoints = [
DataPoint(label: "1月1日", value: 6.0),
DataPoint(label: "1月2日", value: 7.0),
DataPoint(label: "1月3日", value: 8.0),
DataPoint(label: "1月4日", value: 9.0),
DataPoint(label: "1月5日", value: 10.0),
DataPoint(label: "1月6日", value: 6.0),
DataPoint(label: "1月7日", value: 7.0),
DataPoint(label: "1月8日", value: 8.0),
DataPoint(label: "1月9日", value: 9.0),
DataPoint(label: "1月10日", value: 10.0),
]
var body: some View {
BarChartView(
title: "通信量の推移",
xAxisLabel: "日付",
yAxisLabel: "データ量",
yAxisUnit: "GB",
dataPoints: self.dataPoints
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
よくある質問と回答
実装を進める上で生じる疑問について回答します。
1. データポイントの個数が多くなると音声の再生時間が伸びてユーザー体験が悪化する?
先ほどの実装例はデータポイントが10個あり、再生すると約10秒の音声が再生されます。それでは、データポイントの数が100個であれば約100秒の音声が再生されるのでしょうか?
iOSについては現状、音声の再生時間は最大で約10秒です。データポイントが100個であれば、1個あたりの音色の持続時間は約0.1秒になります。
ただし、今後のiOS更新で挙動が変化するかもしれません。また、tvOSやvisionOSについては挙動を未確認です。快適なユーザー体験を追求するのであれば、都度実機で動作検証した方が良さそうです。
2. データポイントの個数は最大いくつまで設定可能か?
不明です。Apple Developerドキュメントにも言及がありません。
iPhone 13の実機で確認したところ、1000個までは再生できることを確認しました。ただし、一度再生すると挙動が不安定になり、再起動するまで2回目以降のオーディオグラフ再生が無音になりました。
3. グラフの詳細画面の多言語対応はどうする?
OSが自動で作成するグラフの詳細画面はデバイスの言語で表示されます。アプリの言語ではありません。
例えばアプリの言語設定が英語、デバイスの言語設定が日本語の場合、VoiceOverカーソルをSwiftUIのビューに合わせると「The x axis is 日付, the y axis is ...」のように英語で読み上げされます。その後にVoiceOverローターアクションでグラフの詳細を選ぶと、日本語で書かれたグラフの概要が表示されます。
おそらく、オーディオグラフはアプリとデバイスの言語設定が異なる状況を想定していません。そもそも、音色でグラフの全体像を大まかに把握するのがオーディオグラフの目的ですから、グラフの詳細画面についてはサポートの対象外として問題ないかもしれません。
Discussion