はじめての VoiceOver 対応


こちらの記事は 「[はじめてのVoiceOver対応 - Speaker Deck](https://speakerdeck.com/ryoabe/hazimetefalsevoiceoverdui-ying)」 の詳細版になります

---

<img src="https://qiita-image-store.s3.amazonaws.com/0/41522/2e5e1384-32bd-a54f-7e80-6c6d4aeafe4a.png" alt="Carlosが使えるように — Apple" width="100%">

# この記事から得られること

- VoiceOver とは何か分かる
- VoiceOver 対応でどういった知識が必要なのか分かる
- VoiceOver 対応ではどんな作業をするのか分かる
- VoiceOver 対応がなぜ必要なのか分かる

# はじめに

<a href="https://www.youtube.com/watch?v=SYUfIaSja40"><img src="https://qiita-image-store.s3.amazonaws.com/0/41522/217d9a9e-27de-75c3-a0b5-c0cdf4e55432.png" alt="Patrickが使えるように — Apple" width="100%"></a>

> [Patrickが使えるように — Apple - YouTube](https://www.youtube.com/watch?v=SYUfIaSja40)

DJでありプロデューサーでもある全盲のPatrick Lafayette氏が、VoiceOver を使いこなしながら音楽制作や美味しそうな料理を作っている動画。すごく活き活きしててかっこいいお父さんですよねー。めっちゃ素敵

（こちらの動画は [シリーズ化](https://www.youtube.com/playlist?list=PLvBsPVtnXtSZ_3Ln8bUZBz_zkAc4__crO) されているので是非他の動画もご覧になってみてください。以前まではもっと動画があったんだけど、いつの間にか減ってしまったな...）

# 私が VoiceOver 対応したアプリの紹介

<a href="https://www.youtube.com/watch?v=8BCnHntPqGo"><img src="https://qiita-image-store.s3.amazonaws.com/0/41522/3c9bf460-72f5-7430-db26-ae4e491a6a48.png" alt="【人事労務freee】iOSの音声読み上げ機能（VoiceOver）のデモ" width="50%"></a>

> [【人事労務freee】iOSの音声読み上げ機能（VoiceOver）のデモ - YouTube](https://www.youtube.com/watch?v=8BCnHntPqGo&t=2s)

私はクライド会計ソフトなどを作っている [freee](https://corp.freee.co.jp/) という会社に務めております。（2018/07/28 現在）
freee ではクライド会計ソフト以外にもう一つメインプロダクトがあり、それがクラウド人事労務ソフトの [人事労務 freee ](https://www.freee.co.jp/hr/) になります。こちらのサービスの iOS アプリを私の方で VoiceOver 対応したのでそこで得た知見をこちらの記事にしようと思いました。

これから VoiceOver 対応を進めるという方は「面倒なことをたくさんしなくてはならないのでは？」と感じているかもしれませんが、カスタマイズの度合いにもよりますが、ちょっとした [label](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619577-accessibilitylabel) や [hint](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619585-accessibilityhint) を正しく設定してあげるだけで大概は済むはずです。

（ちなみに公式ページには [VoiceOverに対応されたアプリ](https://itunes.apple.com/jp/story/id1277246971) が紹介されており、頑張ればここでフィーチャーされたりするかも？）

## そもそも VoiceOver とは？

VoiceOver とは、macOS、iOS、tvOS、watchOS に内蔵されているスクリーンリーダーのことです。（本記事ではこれらの OS のうち、 iOS における VoiceOver について話したいと思います）
VoiceOver がアプリのUIとユーザのタッチの間の仲介役となり、画面上で起きていることや情報を音声で読み上げてくれることで、端末の操作を補助し、アクセシビリティを高めてくれます。

Apple には[視覚のアクセシビリティ](https://www.apple.com/jp/accessibility/iphone/vision/)という公式ページがあり、そこには以下のように書かれています。

<img src="https://qiita-image-store.s3.amazonaws.com/0/41522/98ee715b-d534-f971-fe04-fbceba5d542e.png" width="80%" alt="FaceTimeアプリケーションを使って手話で会話している男性と女性">

> **パーソナルなデバイスを、すべての人が使えるように。**
> 
> 世界で最もパーソナルなデバイスは、すべての人が使えるように設計されました。だから、視覚に障がいのある方がみんなとセルフィーを撮ることも、聴覚に障がいのある方がお母さんに国際電話をかけることも、首から下の体を動かせない方が友だちにテキストメッセージを送ることもできます。

VoiceOver には「すべての人が使えるように」するための機能が組み込まれており、「視覚に障がいがある方」と「アプリ」を VoiceOver によって繋ぐことができます。（逆をいえば VoiceOver 対応がなされていないアプリや機能は「真っ暗」と言えるのかもしれません）

<img src="https://qiita-image-store.s3.amazonaws.com/0/41522/3eaade77-c878-4f95-f054-52c46b7106f5.gif" width="80%" alt="Auditing Your Apps for Accessibility - WWDC 2016 - Videos - Apple Developer - p.17">

> [Auditing Your Apps for Accessibility - WWDC 2016 - Videos - Apple Developer p.17](https://developer.apple.com/videos/play/wwdc2016/407/)

## どうやって VoiceOver は使うの？

詳細は以下の参考をご覧いただければよいですが、ざっくり説明します。

- VoiceOver を On にするには？
	- 「設定」＞「一般」＞「アクセシビリティ」＞「VoiceOver」＞ On
- カーソルを移動するには？
	- 1本指で左右にスワイプ
- 決定するには？
	- 1本指でダブルタップ
- ホームへ移動には？（iPhoneX の場合）
	- 1本指で画面の下部を下から上にスワイプしたまま「ピコっ」と鳴るので手を離すと移動する
	- （初め分からず設定画面から抜け出せなかった...）
- 次のページへ移動（ホーム画面のページ移動のときなど）
	- 3本指で左右にスワイプ

だいたいこれらのジェスチャーでなんとかなる。 [ショートカット](https://help.apple.com/iphone/10/#/iph3e2e31a5) は設定しておくと非常に便利です。

- 参考
	- [iPhone の VoiceOver を使ってみよう! - Qiita](https://qiita.com/labocho/items/b07b4ca821b257bfaa64)
	- [VoiceOver の基本 - iPhone ユーザガイド](https://help.apple.com/iphone/10/#/iph3e2e33be)
	- [‎「視覚障がい者向け使い方教室 for iPhone」をApp Storeで](https://itunes.apple.com/jp/app/id989735084)

## VoiceOver 対応はどういったときに必要になるの？

[iOSアクセシビリティプログラミングガイド p.6](https://developer.apple.com/jp/documentation/iPhoneAccessibility.pdf) に書かれている通りカスタム度合いによって対応範囲が変わります。

- CALayer や OpenGL を使用してUIを提供していることでアクセシイビリティ情報（後述の Label, Traits, Frame など）が無く読み上げられない
- UIKit を使用しているがカスタムのコントローラやビューでUIを提供していて正しく読み上げられない
- 読み上げられているが意図した読み上げられ方ではない
- UIKit 側では適切な読み上げ方が判断できない

これらのケースを具体的な例をあげながら対応方法を説明します。

# 対応する前に知っておくべきこと

## アクセシビリティ属性

[UIAccessibility](https://developer.apple.com/documentation/uikit/accessibility/uiaccessibility) プログラミングインターフェイスを実装することで VoiceOver に必要な情報を伝えられアプリをアクセシブルにすることが可能になります。このインターフェースを一部の UIKit でデフォルトで実装されているため最小限の対応で済みますが、上記の例のようにカスタム度合いによって適宜アプリ側で実装してあげる必要があります。その際に実装に必要になるのが以下のアクセシビリティ属性です。

デフォルト実装されているUIコンポーネントとして分かりやす例が [UIDatePicker](https://developer.apple.com/documentation/uikit/uidatepicker) です。
アラームアプリの時刻のピッカーにフォーカスがあたると、

> "x時。ピッカー項目。 調整可能。値を調整するには1本指で上または下にスワイプします"

と読み上げられます。

これは、

> "[Value] [Label] [Trait] [Hint]"

を指しています。それぞれの属性について詳しく説明していきます。

### [accessibilityLabel](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619577-accessibilitylabel)

UI要素のラベルを表します。

- UISwitch は「切り替えボタン」
- UITextFiled は「テキストフィールド」
- UIButton の title

がラベルに相当します。

> ● Label（ラベル）。コントロールやビューを簡潔に説明する、短く、ローカライズされた単語またはフレーズですが、要素のタイプは識別しません。例としては、「Add（追加）」や「Play（再生）」などです。

### [accessibilityTraits](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619584-accessibilitytraits)


UI要素の種類、状態、実行可能な振る舞い、を複数選択して表します。

- デフォルトでセットされている trait
	- UIButton は [button](https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1620194-button) 
	- UIImageView であれば [image](https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1620174-image) 
	- UIDatePicker であれば [adjustable](https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1620177-adjustable) 


- 動的に変わる trait
	- UITableViewCell や UICollectionViewCell の [isSelected](https://developer.apple.com/documentation/uikit/uitableviewcell/1623263-isselected) が true になると traits に [selected](https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1620197-selected) がセットされる


> ● Traits（特性）。それぞれが要素の状態、動作、または使用方法の特徴の1つを説明する個々の特性を、1つ以上組み合わせたものです。たとえば、キーボードのキーのように動作する要素が現在選択されている場合、KeyboardKey特性およびSelected特性の組み合わせで特徴付けることがで
きます。

### [accessibilityHint](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619585-accessibilityhint)


Label と Trais だけでは補えない情報を付与します。UIButton は適切な Label と Trait が入っていれば認識できますが、 UIDatePicker は表示されたはいいがどのように編集していいかは分かりません。そのためデフォルトで「値を調整するには1本指で上または下にスワイプします」というヒントが入っています。

> ● Hint（ヒント）。要素に対するアクションの結果を説明する、短く、ローカライズされたフレーズです。例としては、「Adds a title（タイトルを追加）」や「Opens the shopping list（ショッピングリストを開く）」などです。

### [accessibilityValue](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619583-accessibilityvalue)


UIButton の場合は空です。値を持つような Picker や Swich、Progress などは値が入ります。

> ● Value（値）。値がラベルで表されていない場合の、要素の現在の値です。たとえば、スライダのラベルは「Speed（速度）」ですが、その現在の値は「50%」であることなどが考えられます。

### [accessibilityFrame](https://developer.apple.com/documentation/uikit/uiaccessibilityelement/1619579-accessibilityframe)



## 読んでおくと良い/見ておくと良い資料
上記で説明したことは最低限のことです。説明しきれていないところも多々あるので [iOSアクセシビリティプログラミングガイド](https://developer.apple.com/jp/documentation/iPhoneAccessibility.pdf) だけでも一読することをオススメします。

- [iOSアクセシビリティプログラミングガイド](https://developer.apple.com/jp/documentation/iPhoneAccessibility.pdf)
- [iOS View Controllerプログラミングガイド: アクセシビリティをサポートする](https://developer.apple.com/jp/documentation/featuredarticles/ViewControllerPGforiPhoneOS/SupportingAccessibility/SupportingAccessibility.html#//apple_ref/doc/uid/TP40007457-CH12-SW1)
- [iOSのアクセシビリティ(VoiceOver)対応まとめ - Qiita](https://qiita.com/nagasakulllo/items/25ed04954650994991fe)
- [iOSアプリのアクセシビリティ向上に役立つ「Accessibility Inspector」の紹介 | スタッフブログ | LAB inc.（株式会社ラボ）](https://lab-inc.jp/staffblog/app/ios/201610/18_57.html)
- [Auditing Your Apps for Accessibility - WWDC 2016 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2016/407/)
- [What's New in Accessibility - WWDC 2017 - Videos - Apple Developer](https://developer.apple.com/videos/play/wwdc2017/215/)


# VoiceOver 対応でやったこと

やっと本題です。具体例を上げながら対応方法を説明していきます。

### 画像ボタンには label セットが必須

<img width="300" src="https://qiita-image-store.s3.amazonaws.com/0/41522/8d0c3ae8-ce3b-eed2-e3d7-2f13eb89ffe1.png" alt="任意の画像を使ったボタン" style="border: 0.5px solid">

任意の画像を使ったボタンを読み上げてみると「ボタン」とだけ trait が読み上げられます。このままではどういったボタンなのか分からず怖くてタップできません。そういった場合にこの例だと "ブックマーク" とラベルをセットしてあげることで「ブックマーク ボタン」と読み上げてくれるようになります。

- **意識していないと平気で発生するし、詰む可能性が高いので注意が必要**
- HTMLでいう img タグに alt 属性が入ってのと同じ
- system type の画像ボタンは不要


### 意図しない読み上げられ方をした場合にも適切な label に変更

<img width="300" src="https://qiita-image-store.s3.amazonaws.com/0/41522/c2d30ad3-fba9-8de6-8bd9-b2161d1ebe04.png" alt="6/5 〜 6/8 と書かれたUILabel" style="border: 0.5px solid">

上記の例はリファレンスやプログラミングガイドにも書かれている内容ですが、その他にも accessibilityLabel を独自に設定してあげたほうが良いケースがあります。それは、こちらが意図しない形で読み上げられてしまっているケースです。

例えば、「6/5〜6/8」と期間が書かれたラベルがあったとします。この場合、「ろくがついつか から ろくがつようか」と読まれることをこちらとしては想定していますが、実際には **「ろくスラッシュご ろくスラッシュはち」** といったように、「/」は「すらっしゅ」、「〜」は読み飛ばされ読み上げられてしまいます。利用しているアプリ内でユーザ自身で設定した期間などだったら理解できるかもしれませんが、アプリ側から表示されたものだった場合は、「/」で日付であることはギリギリ理解できても、「〜」が読み飛ばせれてしまっているため期間であることを理解できない可能性があります。

こういった場合に **accessibilityLabel に「6月5日から6月8日」と設定** しておくと「ろくがついつか から ろくがつようか」と意図した形で読み上げられるようになります。
（ローカライズは適宜）

### traits は振る舞いに沿って設定する①

<img alt="設定アプリのApple ID、サインアウトのセル" width="300" src="https://qiita-image-store.s3.amazonaws.com/0/41522/49996358-21f5-6501-de63-ba8de75e9e93.png" style="border: 0.5px solid">

> 設定 > Apple ID > サインアウト セル

「サインアウト」セルのようなケースです。UITableViewCell にはデフォルトでは traits は何も指定されていません。このケースだと traits に button が指定されていることが望ましいが、無指定のままだとフォーカスが当たっても「ボタン」と読み上げられず、利用者にアクセシブルであることが伝わりにくくなってしまうため、適切な trails を追加してあげる必要があります。
（[accessoryType](https://developer.apple.com/documentation/uikit/uitableviewcell/1623228-accessorytype) が指定されている場合にはタップ可能とOS側で判断され、 trails に button を追加しなくても「ボタン」と読み上げてくれます）


### traits は振る舞いに沿って設定する②

<img alt="App Store マイアカウント アカウント" width="300" src="https://qiita-image-store.s3.amazonaws.com/0/41522/ba633e83-7ef6-32ff-41d2-ccedecd77c53.png" style="border: 0.5px solid">

> App Store > マイアカウント > アカウント > サービス規約

同じような形状のセルでも `button` を指定してあげれば良いとは限りません。以下のセルはタップすると Web ページが表示されます。そのため適切な trait は `button` ではなく [link](https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1620178-link) になります。

### hint がないと詰んでしまうかも

<img alt="カレンダーアプリの開始セル" width="300" src="https://qiita-image-store.s3.amazonaws.com/0/41522/46f552d0-84c0-4597-0403-5efc3925352f.png" style="border: 0.5px solid">

> カレンダー > 予定追加 > 開始/終了 セル


カレンダーアプリの開始日時や終了日時はタップすることでピッカーが表示されます。この編集方法を開始/終了の日付セルにフォーカスを当てたときに「編集するにはダブルタップします」と読み上げられるよう hint がセットされています。
このように label や trait では伝えきれない部分を hint を用いて補ってあげる必要があります。



### extension を作っておくと便利

- 読み上げメソッド

```swift
extension String {
    func say() {
        UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self)
    }
}
```

- フォーカス移動メソッド

```swift
extension UIView {
    func focus() {
        UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self)
    }
}
```


##  Accessibility Inspector をうまく使おう

### Inspection

<img alt="Accessibility Inspector Inspection" width="600" src="https://qiita-image-store.s3.amazonaws.com/0/41522/b190196f-0b48-bea6-7ce3-bd2ef48338f2.png">


> Xcode > Open Developer Tool > Accessibility Inspector > Inspection Tab より利用できます

Inspection を使用することで VoiceOver を実機で使用せずとも label や value、hint の情報が分かるので便利です。
（ただ最終的には実際の使用感を確かめるために実機での確認は必須ですけど）

### Audit

<img alt="Accessibility Inspector Audit" width="300" src="https://qiita-image-store.s3.amazonaws.com/0/41522/c2538b03-52c1-484e-6915-1fb1d9959590.png">

> Xcode > Open Developer Tool > Accessibility Inspector > Audit Tab > Run Audit より利用できます

VoiceOver に限らずアクセシビリティ対応全般の不備のチェックが可能で、「description がないよ」、「タップエリアが狭いよ」、「Dynamic Type に対応されていないよ」などといったこを教えてくれます。
なので初めにざっと流してみると良いかもしれません。

（ただ、なぜか他のアプリに影響が出たりして、僕の環境だと Accessibility Inspector 起動中は Alfred が動かなくなったりします）


# なぜ freee は VoiceOver 対応するのか

- 少し手を加えてあげるだけで利用可能なユーザを増やすことができる
- おまけに、UI設計の知見が深まる
- freeeは事業として取り組んでいる
- 全盲のエンジニアも社内に居てアクセシビリティ向上に取り組んでいる

<img src="https://qiita-image-store.s3.amazonaws.com/0/41522/9dd24b32-7b76-f611-dc21-ec4f59da585d.jpeg" alt="誰か􏰁ためで􏰀なく将来􏰁自分􏰁ために">

この言葉は同僚の全盲のエンジニアから頂いた言葉です。僕はこの言葉でやっていく気持ちが高まりました🔥🔥🔥
この言葉と共にこれから VoiceOver 対応される方の一助にあれば幸いです。