私はモバイルアプリにおけるアクセシビリティ対応の記事をいくつか投稿しています。
- モバイルアプリでアクセシビリティ対応をする前に知るべきこと
- Xamarin.Formsにおけるスクリーンリーダー対応まとめ ←今ココ
「モバイルアプリでアクセシビリティ対応をする前に知るべきこと」を読んでから本記事を読むことをオススメします。
はじめに
Xamarin.Formsにおけるスクリーンリーダー(iOS: VoiceOver、Android: TalkBack)対応について紹介します。
注意
Xamarin.Formsでアクセシビリティ対応するのは 非常に難しい です。
そもそもアクセシビリティ対応まで考えて設計されたフレームワークではなく、2022/03/07現在の最新バージョンでもできないことや不安定なことが多いです。
Xamarin.Formsの後継であるMAUIは、最初からアクセシビリティが考慮されています。
アクセシビリティ対応が必要なアプリの場合、MAUIが正式リリースされたら早めに移行するのがいいと思います。
環境
- Xamarin.Forms:5.0.0.2291
- Xamarin.CommunityToolkit:2.0.0
Xamarin.Formsにおけるスクリーンリーダー対応
Xamarin.Formsにおけるスクリーンリーダー対応を順番に紹介します。
要素にアクセスできるようにする
AutomationProperties.IsInAccessibleTree 属性に True を指定することで、スクリーンリーダーで読み上げられ、遷移されるようになります。
指定しなくてもアクセスできる要素もありますが、明示的に指定したほうが確実です。
<Label
+ AutomationProperties.IsInAccessibleTree="True"
Text="{x:Static resources:AppResources.FooText}" />
装飾用の画像など、逆にスクリーンリーダーでスキップしたい要素には False を指定します。
非表示の要素にも False を指定しないと遷移されることがあるので注意です。
要素の読み上げを設定する
デフォルト(ラベルなら Text )以外の読み上げを設定したい場合、 AutomationProperties.Name 属性に文字列を指定します。
<Label
AutomationProperties.IsInAccessibleTree="True"
+ AutomationProperties.Name="テスト"
Text="{x:Static resources:AppResources.FooText}" />
AutomationId 属性が指定されているとなぜかそれが読み上げられるので、その場合は指定が必須です。
Androidのラベルでは Text と AutomationProperties.Name の両方が読み上げられます。
実装を工夫してiOSのみ AutomationProperties.Name を指定することはできますが、Androidでは読み上げを変えられないので、根本的な解決にはなりません。
<Label
AutomationProperties.IsInAccessibleTree="True"
Text="{x:Static resources:AppResources.FooText}">
+ <AutomationProperties.Name>
+ <OnPlatform x:TypeArguments="x:String">
+ <On Platform="iOS" Value="Foo" />
+ </OnPlatform>
+ </AutomationProperties.Name>
</Label>
このあたりの読み上げについて詳しい方がいたら教えていただけると嬉しいです。
要素の遷移順を決める
遷移順を明示的に指定しないと、きれいに左上から右下に向かって遷移しなかったり、要素間でループして他の要素へ遷移しなかったりします。
遷移したい全要素に TabIndex を指定することで、要素の遷移順を確定できます。
<Label
AutomationProperties.IsInAccessibleTree="True"
+ TabIndex="1"
Text="{x:Static resources:AppResources.FooText}" />
<Label
AutomationProperties.IsInAccessibleTree="True"
+ TabIndex="2"
Text="{x:Static resources:AppResources.BarText}" />
Androidだと指定通りに遷移しないことがあるので注意です。
TabIndex は以下の要素に付けても効きません。
StackLayoutCollectionView
TabIndex を指定することで、iOSで最初と最後の要素がお互いに遷移しなくなります。
スクリーンリーダーのユーザーは、無限ループすると最初と最後の要素がわからなくなって困るので、ありがたい副作用です。
要素にフォーカスを当てる
Xamarin.CommunityToolkitの1.3.2 で SetSemanticFocus() メソッドが追加されました。
このメソッドを呼び出すと、引数で渡した VisualElement にフォーカスを当てられます。
基本的には呼び出さないようにしたいですが、どうしてもフォーカスが当たらない要素に対しては有用です。
例えば MasterDetailPage を使ってメニューを実現する場合、iOSではメニューから次のページへ遷移してもフォーカスがメニューに残ってしまいます。
遷移先の ContentPage の OnAppearing() 内で SetSemanticFocus(this) を呼び出すことで、フォーカスを次のページに当てられます。
+ using Xamarin.CommunityToolkit.Extensions;
public partial class FooPage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
+ Device.BeginInvokeOnMainThread(() =>
+ {
+ SemanticExtensions.SetSemanticFocus(this);
+ });
}
}
Xamarin.Forms.Device.BeginInvokeOnMainThread(Action) で括らないと、Androidで System.NullReferenceException: Can't access view が発生するので注意です。
Androidでは OnAppearing() を抜けたあとに実行される必要があるようです。
これでもフォーカスがメニューに残ってしまうページがあるので、もっといい回避策や根本的な解決策があれば知りたいです。
ちなみに SetSemanticFocus() は公式ドキュメントがありません。
MAUIの公式ドキュメントにはあるので、挙動は違う可能性がありますが、参考になります。
ListViewの小技
ListView に関するアクセシビリティ対応を紹介します。
読み上げを調整する
iOSではリストへの遷移時に「カラノリスト(おそらく「空のリスト」)」と読み上げられてしまうことがあります。
ListView の AutomationProperties.Name に文字列(ここでは「リスト」)を指定することで回避できます。
<ListView
+ AutomationProperties.IsInAccessibleTree="True"
+ AutomationProperties.Name="{x:Static resources:AppResources.List}">
</ListView>
+ <data name="List" xml:space="preserve">
+ <value>リスト</value>
+ <comment>リスト</comment>
+ </data>
Androidで読み上げがおかしくなるなどの副作用があるので、もっといい回避策や根本的な解決策があれば知りたいです。
フッターと空の要素の両方を表示しない
iOS 14以下では、リストにフッターがないと空の要素が一番下の要素の下に表示されます。
Fotter="" を付けると回避できますが、リストの一番下の見えないフッターに遷移できてしまいます。
この問題を解消するために、遷移できない空のフッターを追加します。
<ListView>
+ <ListView.Footer>
+ <StackLayout AutomationProperties.IsInAccessibleTree="False" />
+ </ListView.Footer>
</ListView>
もっといい回避策や根本的な解決策があれば知りたいです。
おまけ:属性の順番
XAMLでタグ内の属性について、私は以下の順番で書いています。
x:NameGrid.RowGrid.ColumnAutomationProperties.IsInAccessibleTreeAutomationProperties.NameTabIndex- その他
他にオススメの順番があれば教えてほしいです。
おわりに
これでXamarin.Forms製のアプリのアクセシビリティを向上できました!
ただ本記事を読んでわかる通り、まだ難しいことがたくさんあるので、もっと向上できるよう頑張ります。