みなさんはアクセシビリティを考えてアプリを作っていますか?今回はアクセシビリティを考慮したアプリに関わる可能性があり、実装したことがなかったので調査しました。
アクセシビリティとは
Appleが発表しているアクセシビリティの機能として
- 視覚
- 拡大鏡
- フォントを太くする
- VoiceOver
- 聴覚
- 光や振動での通知
- サウンド認識でのドアホンや炎の音などの通知
- ヘッドフォンの音量調整
- 身体機能
- 背面タップでのアクション実行
- Siriとショートカットの連携
- アイトラッキングでのコントロール
などといった様々な機能が存在します。アクセシビリティ機能は障がいのある方も含め、すべてのユーザーに高品質な体験を届けるための補助機能として存在しています。
その中でも視覚に障がいを持つ方を補助する際に特に大切な機能であるVoiceOverについて今回調べていきます。
VoiceOver
VoiceOverは、画面が見えなくてもAppのインターフェイスを体験できるよう、画面の表示内容の説明を読み上げる機能です。
https://developer.apple.com/jp/accessibility/
公式によるとUIを画面を見ずとも理解できるように説明してくれる機能のようです。つまり開発者側はVoiceOverで発声する文言を注意して決定していく必要があると言えますね。
VoiceOverを有効化、無効化する方法
- Siriを起動し、「VoiceOverをオンにして」または「VoiceOverをオフにして」と言う。
- サイドボタンをトリプルクリックする(Face IDを搭載したiPhoneの場合)。
- ホームボタンをトリプルクリックする(ホームボタンを搭載したiPhoneの場合)。
- コントロールセンターを使用する。
- 「設定」 >「アクセシビリティ」>「VoiceOver」と選択してから、設定をオンまたはオフにする。
これら5つの方法があるようです。試しに利用する際には無効化する方法を覚えておかないと普段の操作とはまるで違うので困ってしまいます。
操作方法
使う際の基本的な操作方法は以下のようになっています。
フォーカスの移動 | 1本指でスワイプ操作 |
要素の選択 | 1本指でタップ操作 |
要素の決定 | 1本指でダブルタップ操作 |
読み上げ対象となるUIパーツ
基本的にUIKitに含まれているものであればUIAccessibility
プロトコルに準拠しているので次のインスタンスメソッドを利用して有効化することですぐに読み上げられます。
Storyboardを利用して配置したUIパーツは基本的にデフォルトでAccessibilityが有効になっています。(UIView
は無効がデフォルト)
またSwiftUIにて実装したものに関してもアクセシビリティ要素については自動で生成されるようです。
isAccessibilityElement
意図的にVoiceOverの対象に入れるような場合に明示的にYESを指定します。(UIView
のデフォルトはNO)
また親ViewでこのプロパティがYESの場合には子ViewはVoiceOverの読み上げ対象になりません。説明のためのGifを以下に示します。


作成したサンプルアプリでは親Viewとして赤と青の背景色で設定したUIView
を置き、その中にUILabel
やUIButton
などを用意しています。
左のgifでは親Viewのみにフォーカスが当たっていて、中のUILabelやUIButtonにフォーカスが当たっていないことがわかるかと思います。(UILabelなどがVoiceOverに読んでもらえない)
右のgifでは親Viewにはフォーカスが当たらず、中のUILabelやUIButtonにフォーカスが当たっていることがわかるかと思います。(UILabelなどがVoiceOverに読んでもらえる)
下の二つのUISwitch
では、親UIView
のisAccessibilityElement
を以下のように変更しています。
@IBAction func toggleStoryBoardViewAccessibility(_ sender: UISwitch) {
baseView.isAccessibilityElement = sender.isOn
}
このように親ViewがAccessibilityElement
と認識されてしまうと内部の要素が読まれない、という状況になるため注意が必要です。
読み上げの内容
UIAccessibility
を利用してVoiceOverはユーザーにテキストを提示します。UIAccessibility
のプロパティの中でもaccessibilityLabel
は読み上げ内容を、accessibilityHint
は少し長い詳細な説明を設定できます。
accessibilityTrait
では要素がどのように動作するかが表されています。いくつか種類があるので詳細はドキュメントを参照すると良いでしょう。こちらも目的に応じて自作ビューでは適切に選択すると効果的です。https://developer.apple.com/documentation/uikit/uiaccessibilitytraits
読み上げの内容と順序は [Label][Value][Trait][Hint]
となっています。VoiceOverをオンにしてアクセシビリティの以下の画面の読み上げ速度の調整バーを選択してみてください。

「読み上げ速度、50%、調整可能、値を調整するには1本指で上または下にスワイプします」
と読み上げるはずです。ユーザーはLabel
を順々に聞いていき目的の項目に移動することになるので、素早く項目にたどり着くためにLabelでの端的な説明を求められることがこの読み上げ順序から分かると思います。
具体的な実装を示すため、先のサンプル画面下部のAccessibilityをトグルさせていたUISwitchを以下のコードで喋らせてみます。
private func setupAccessibility() {
storyboardAccessibilitySwitch.isAccessibilityElement = true
storyboardAccessibilitySwitch.accessibilityLabel = "赤色Viewのアクセシビリティ"
storyboardAccessibilitySwitch.accessibilityTraits = .none
storyboardAccessibilitySwitch.accessibilityHint = "赤色の親Viewのアクセシビリティ機能を制御します"
}
これを実装してUISwitchにカーソルを当てると
「赤色Viewのアクセシビリティ、1、赤色の親Viewのアクセシビリティ機能を制御します」
と喋るようになります。(traitについては今回.none
を指定しているので省略して読まれます)
読み上げの順序
基本は左から右、上から下に読み上げますが、要素の配置によっては逆に読んでしまうことがあります。またまとめて読んでほしい要素もあると思います。その場合UIViewのaccessibilityElementsをオーバーライドして要素をグループとして定義することであるまとまりごとで読んでくれるようになります。
例として先のサンプル画面下部のトグルスイッチを見てみましょう。(音が出ます)
「左から右、上から下に読み上げ」の原則から、何もしない場合
IB Accessibility
-> SwiftUI Accessibility
-> 左Switch -> 右スイッチ
と読まれてしまいます。
IB Accessibility
-> 左Switch -> SwiftUI Accessibility
-> 右スイッチ
のように項目名と内容が連続して読まれる方が自然ですよね。
このように対応したものが以下の動画です。(音が出ます) 違いをわかりやすくするために左のみ対応しました。
左はまとまってフォーカスされてラベル -> スイッチの順で喋っているのに対し、右はまずラベルにフォーカスが当たり、ユーザーが操作してスイッチにフォーカスしているのがわかりますね。
実装例として以下に示します。UILabelやUISwitch
をラップしたToggleView
を定義しており、label
とtoggle
をまとめたibElement
のようにまとめる項目ごとに作成し、accessibilityElements
をgetterで返すような実装をすることで実現しています。
class ToggleView: UIView {
@IBOutlet weak var label: UILabel!
@IBOutlet weak var toggle: UISwitch!
private var _accessibilityElements:[Any]?
override var accessibilityElements: [Any]? {
set {
_accessibilityElements = newValue
}
get {
if let _accessibilityElements = _accessibilityElements {
return _accessibilityElements
}
var elements = [UIAccessibilityElement]()
let ibElement = UIAccessibilityElement(accessibilityContainer: self)
//ここでUILabelやUIToggleの文言やTraitなどを拡張したViewでも引き継いでいる
ibElement.accessibilityLabel = label.text
ibElement.accessibilityTraits = toggle.accessibilityTraits
// 作成されるViewのフォーカスされるサイズを中身の要素のframeを結合したサイズに設定
ibElement.accessibilityFrameInContainerSpace = label.frame.union(toggle.frame)
elements.append(ibElement)
_accessibilityElements = elements
return _accessibilityElements
}
}
}
気をつけたいこととしてはUIAccessibilityElement
のArray
に追加した順に遷移していくので、複数のViewをまとめて対応する場合でも「左から右、上から下に読み上げ」を遵守することが挙げられます。日本語のアプリで縦書きでもないのに右から左にものが並んでいたら違和感がありますよね。
おまけ:VoiceOver 認識
iOS14、iPadOS14以降の特定のデバイスにおいて前述の対応をしていなくとも同様な読み上げをしてくれる機能があります。
デバイスベースでAIが認識して対応してくれるようです。
まとめ
事前に検討が必要な点について
- どういう要素をVoiceOver対応するのか、切り分けること
- 要素が読み上げる
hint
の内容検討(場合によってはローカライズも必要) - エンジニア側で見て独自Viewを定義している箇所があれば洗い出しておく
感想
調べる間に、実装して動かしてみて浮き出てくる問題点がありそれをどうにか潰している先行記事をいくつも見かけたので、作って実際に触らないとわからない使い勝手の良さ悪さはあると感じました。自分では使わない機能ですが、必要な人が使う際にこの機能がちゃんとあることが重要だとVoice Over機能を調べてみて理解しました。
公式サンプルが単純な実装を説明してくれていて、初学者こそ公式ページを参照する重要性を感じました。
参考文献
- UIKit対象のAccessibility公式ドキュメント
- VoiceOverの使い方
- 独自ViewをAccessibility対応にするサンプル