iOS のコンテンツ読み上げ機能の実装の仕方を調べる
目的
普段は iOS の Kindle アプリで読み上げコンテンツ機能を使って読書しているが、実装がどれくらい難しいのか知りたかった。
特に知りたいのは、以下の 2点
- コンポーネントをコンテンツ読み上げの対象にするにはどうすればいいか
- Kindle アプリのように、ページ内のコンテンツを読み上げ終わった際に、システムで次のページに遷移するにはどうすればいいか
コンポーネントをコンテンツ読み上げの対象にするには
UIView に用意されている アクセシビリティ関連のプロパティを使用する
以下のように UIView の isAccessibilityElement
を true
にするだけでコンテンツ読み上げ対象になる。
(UIAccessibility のプロパティのようですが、継承関係は調べてません)
let uiView = UIView()
// この設定をするだけで、読み上げ対象になる
uiView.isAccessibilityElement = true
さらに読み上げ内容を指定したい場合は、上記に加えて accessibilityValue
に文字列の内容を指定する。
let uiView = UIView()
uiView.isAccessibilityElement = true
uiView.accessibilityValue = "読み上げて欲しい内容です。"
デモ
ページ内のコンテンツを読み上げ終わった際に、次のページに遷移するにはどうすればいいか
処理の内容
考え方としては、
- ページのコンテンツが読み終わった際に、ページ送りする処理を呼び出す。
- ページ送り後、次のページのコンテンツ読み上げを開始する。
これを繰り返すと本を一冊読み上げることも出来る。
iOS のアクセシビリティの文脈
上記を iOS のアクセシビリティの文脈で読み替えると、以下のようになる。
- UIView がコンテンツ読み上げ終了時に呼び出すコールバックからページ送りの処理を呼び出す
- ページ送り後、ページが変更されたことを iOS のアクセシビリティ機能に伝える
準備
読み上げ対象の UIView の accessibilityTraits
に .causesPageTurn
を設定をする。
(本だと「1ページ」分のコンテンツ)
let uiView = UIView()
uiView.isAccessibilityElement = true
uiView.accessibilityValue = text
uiView.accessibilityTraits = .causesPageTurn // <= NEW!
この設定を行うことで、コンテンツ読み終わった際にコールバックを使って、次のページに進む処理を挟めるようになる。
ページ送りの実装方法
上記の準備が完了していれば、accessibilityScroll
というコールバックを使用することができる。
public class SomeUIView: UIView {
public override func accessibilityScroll(
_ direction: UIAccessibilityScrollDirection) -> Bool {
switch direction {
case .next, .right:
// ページ送りの処理を書く
return true
default:
return false
}
}
}
実装の詳細
自分の場合はページ送り用の処理を UIView 継承クラスに渡して、コンテンツ読み上げ時に処理を呼び出すようにした。
public class SomeUIView: UIView {
// ページ送り用の処理を渡す
public var goToNextPage: ()-> Void
...
public override func accessibilityScroll(
_ direction: UIAccessibilityScrollDirection) -> Bool {
switch direction {
case .next, .right:
// ページ送りを伝える処理
goToNextPage()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
// ページが切り替わったことを iOS のアクセシビリティ機能に伝える処理
UIAccessibility.post(
notification: .pageScrolled, argument: nil
)
}
return true
default:
return false
}
}
}
UIAccessibility.post(notification: .pageScrolled, argument: nil)
で、アクセシビリティ機能にコンテンツが切り替わったことを伝えることができる。
自分が試した時は、goToNextPage 呼び出し直後に呼び出すと同じページを二回読んでしまう現象が発生した。おそらく、ページ送りの処理呼び出し直後だと、コンテンツが切り替わっていないからだと思われる。
上記のように DispatchQueue
で呼び出しを遅らせることで発生しなくなった。