ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
watchOSにおけるアクセシブルな体験の実現
大きなテキストサイズ、VoiceOver、AssistiveTouchなどの機能をサポートする場合に、watchOS向けに最高のアクセシビリティ・エクスペリエンスを構築する方法を紹介します。APIのインテグレーション、エクスペリエンスなどに関するベストプラクティスを含め、watchOS用に構築されたSwiftUI Appに視覚および身体機能に関するアクセシビリティのサポートを追加する方法を説明します。
リソース
関連ビデオ
WWDC21
WWDC20
WWDC19
-
ダウンロード
♪ (watchOSにおける アクセシブルな体験の実現) こんにちは ダニエルです アクセシビリティエンジニアです 本トークでは 私の同僚ヴィラータと共に watchOSの アクセシビリティ機能と これらを使うユーザー向けに デベロッパとしてどのように watch Appを構築するかに ついて説明します 本日は まずwatchOSにおける アクセシビリティを紹介します 次にアクセシビリティAPIに ついて詳しく説明し さまざまなビジュアル アクセシビリティ サポートのために 可能なことをお伝えします そして最後に ヴィラータがWatchの 運動障害のための アクセシビリティ動作や そのサポートのために できることを説明します まずはwatchOSの アクセシビリティです アクセシビリティとは 個々のユーザーがデバイスを 使いやすいようにすることです つまりAppで最高の ユーザー体験を提供するには アクセシビリティへの 配慮が欠かせません Apple Watchには Appを使いやすくする アクセシビリティ機能が 多数用意されています VoiceOverなどの アシスト技術により たとえば視覚障害のある ユーザーが ジェスチャで画面を操作し コンテンツ読み上げ中の タップで Apple Watchを フル活用できます 今年のwatchOSには Apple Watch専用に 再開発された AssitiveTouchが 加わりました AssitiveTouchにより 運動障害のある人が 画面にまったく 触れずに Apple Watchを 使えます この機能については ヴィラータが後で説明します サポートするために できることについても説明します watchOSは モーション低減 太字などの 表示への適応を 提供しています アクセシビリティテキスト サイズも大きくなりました ここでは視覚的な アクセシビリティを説明します 適切なAPIで アクセシビリティを サポートすると AppがWatchKitや SwiftUIで記述されているか どうかに関わらず VoiceOverが正しく 機能するようになります SwiftUIのアクセシビリティに ついて説明します ただしここで学習する すべての原則が WatchKitにも適用されると 理解しておいてください またAppを アクセス可能にする際には コンプリケーションや通知にも 配慮してください これらはAppからの コンテンツを配信する 別経路として 機能するため 私たちのアシスト技術を サポートするようにしてください Appの構築のために 新しいことを学べる 一番の方法では ないでしょうか? 幸運にも私は家で 植物に投資するように なったので その世話用Appを 作ることにしました まだ完成していませんが メイン画面では 今後の植物の世話の スケジュール (5日間の水やり 7日間の肥料やり 日光に適度に当てる)など 育てている植物の 全情報が表示されます ここに他の植物があります 植物への水やりや 施肥のような 作業記録に使う 2つのボタンがあります そしてセルを タップして 水やりや施肥の 間隔を調節できます これは情報提供 Appなので 各セルにはテキストが かなり入っています ここではシステムテキストが デフォルトサイズの場合の Appの外観をお見せします 見た目はかなり良いです しかし左のように システムテキストサイズを 小さくすると ボタンとタスクリストの テキストサイズが 変更されても 植物名の文字サイズは 変わりません 次に右のアクセシビリティを 大きくすると タスクリストの テキストサイズが大きくなり 情報が画面に 収まらなくなります ではAppに Dynamic Typeを適切に サポートさせる方法を 見てみましょう PlantViewのコードを 調べると タイトルやその他コンテンツを 含む VStackがあります タイトルに使った フォントサイズが 固定されていることに 注目してください それは完全に私のタイトルの サイズの変更を止めています 固定フォントサイズではなく 提供されている11種類の テキストスタイルのいずれかを 使う必要があります 左側のテキストスタイルは デフォルトのシステム テキストサイズで表示されます テキストサイズを 最大にすると右側の テキストスタイルのサイズに 合わせて拡大されます テキストスタイルを使うと システムのテキスト サイズ設定に合わせて フォントサイズが 自動で調整されます したがってPlantViewコードを もう一度見てみると タイトルのフォントを タイトル3の テキストスタイルに 変更してサイズの小さい タイトルにすることで すばやく簡単に変更できます 次にタスクリストの テキストの切り捨てを修正します PlantTaskLabelのコードでは HStack内のすべての項目に 1のlineLimitを 割り当てます これでテキストを1行に することのみが可能になります UIで必要な数の行に 柔軟に調整できるように するにはサポートの必要が ある最大行数に lineLimitを設定します または削除して行数を 制限しないようにします 今 私たちは進歩しています しかしUIは確かに大きく 切り捨てもありませんが プロセスでは非常に 詰め込まれています 画面の情報を読むのは まだ簡単ではありません 大きなテキストスタイルの レイアウトでは 構造を変える必要が ある場合があります したがってレイアウト構築時 sizeCategoryに Environmentプロパティ ラッパーを作成し この変更があったときに 更新を取得します 次にsizeCategoryに 依存するよう UIを調整するのみです この場合 タスクリスト内の ExtraLargeの どこかでテキストが折り返されます サイズカテゴリが これより小さい場合は 前に見た PlantViewを使います ただしそれが大きい場合は この新しい垂直方向の ビューを使います このビューでは 各ラベルとボタンを並べて より多くのスペースを確保できます こちらの方がきれいに見えます Watchへの 大きなアクセシビリティ テキストスタイル導入により ダイナミックタイプの 利用者の増加が予想されます あなたもそうすべきです! Apple Watchの設定をする ユーザーには 開始時にテキストサイズを カスタマイズする オプションが表示されます 変更を行わない場合は スマートフォンで 使われているサイズに 最も近いサイズを Watchが自動で 選択します テキストサイズが大きい 場合にWatch Appが 優れた動作をするようにするには Dynamic Typeの 概要を説明するのに 重要な点が3つあります まず固定フォント サイズではなく常に テキストスタイルを 使うようにします 次にテキストを折り返して 切れないようにします 3つ目は必要に応じて コンテンツが 詰まりすぎたときに 垂直方向のレイアウトに 切り替えることです ここでは大きなテキストに関して 簡単な説明をしただけなので "Building Apps with Dynamic Type"という トークをご確認いただき ステップアップいただくことを 強くおすすめします また優れたビジュアル体験の 実現について さらに詳しく知るには "Appを視覚的に アクセシブルにする" というトークをご覧ください これで視覚的に うまく動作します しかし目の見えないユーザーは どうすればよいでしょうか VoiceOverをオンにして 体験を聞いてみましょう
WWDaisy デイジー イメージ ドロップ イメージ 5日間 リーフ イメージ 7日間 もっと明るく イメージ 中程度 ドロップ フィル ボタン リーフ フィル ボタン 従って私達が できる改善点は必ずあります 1つ目の方法は 操作可能なボタンの 数を減らすことです 現時点ではこれは かなり複雑なビューです 各セルに4つのラベル 4つの画像ボタンが2つあります 現在2番目の植物に移るには 最初の植物セルの 全項目を通過する必要があります また水 肥料 日光の画像アイコンも テキストに別々の 要素として読み上げられ ラベルは文脈に 合っていません 最後に下2つのボタンは 使われているシンボルが 提供するデフォルトの ラベルを使っていました 最初にお話ししたのは 植物1から植物2に 移るには移動 すべき項目が 多すぎるということです NavigationLink 作成時は各要素の アクセシビリティステートの 理解のために子の アクセシビリティElement グループを .containとして 指定しました しかしこれはいいように 見えるので この行を削除します NavigationLinkは 子からの すべてのアクセシビリティ情報を 自動で結合します これでセルは 単一要素として扱われ 読み上げ内容は"WWDaisy 5日間 7日間 中程度 ボタン" となります このタスクリストには まだ作業が必要です 各タスクに適切な ラベルを付けて 今後の植物の世話タスクの 状況を説明します PlantTaskがラベルを 決めます PlantTaskLabel構造体の 内部ではaccessibilityLabelを 変更して各植物タスクに 異なる文字列を 返すようにします これはボタンに ラベルを置くのに 使う技術と同じです コンテンツは "WWDaisy 5日間水やり 7日間施肥 中程度の日光に当てるボタン" と読み上げられます 水やりと施肥ボタンは "水やり記録ボタン" "施肥ボタン"と 読み上げられます これまで多くの作業が 自動で行われていますが 必要な追加の修飾子は わずかです SwiftUIではほとんどの アクセシビリティ機能が 無料です 数行コードを 書くだけで済むでしょう しかしこれから常に カスタムコントロール 構築の必要がある 場合もあります 私は植物のための 水やりと施肥の頻度を 調節できるように カスタムカウンタを 作ることにしました では見てみましょう 数日間の水やりの頻度 見出し 削除 ボタン 8 追加 ボタン 追加 技術的には 機能するものの 理想の体験ではありません ここでの目標は これら3項目を1つの アクセス可能な要素に することです まず アクセシビリティElement 修飾子を使います これで新しい 上位レベルの要素が 作成されますが今回は すべての子を無視します これは アクセシビリティElementの デフォルト動作です 同じ動作を得るには パラメータを空白の ままにしておけばいいのです 子を無視しているため 自動で提供された アクセシビリティ アクションとともに ボタンの ラベルが破棄されます 代わりに accessibilityAdjustableActionを 使ってユーザがカウンタを 上下にスワイプして 値を増減できるようにします 要素が1つしかないため タスク名を使って ラベルを1つだけ付けます これは"水やり頻度"か "施肥頻度"となります 最後に値を入力します accessibilityValueは 変更のたびに 読み込まれますが ラベルは要素の 移動時のみに 読み上げられます
数日間の水やりの頻度 見出し 水やりの頻度8日間 調整可能 9日間 素晴らしい よりよく動くようになりました 先ほど見たようにSwiftUIを 使えばVoiceOverが Watch Appに簡単に アクセスできるようになるのです またSwiftUIであるため macOSとiOSでも 同じコードが使えます SwiftUIで素晴らしい体験を デザインする詳細には トーク"SwiftUIの アクセシビリティ"の 説明をご覧ください SwiftUIアクセシビリティ 使用時の新しいツールと APIについてはトーク "SwiftUI アクセシビリティ: 基礎を超えて"を ご覧ください 次に視覚的アクセシビリティから 移る前に コンプリケーションと通知という 2点に焦点を当てます コンプリケーションはAppへの トラフィックが多いため アクセスしやすい方法で 情報を提供する 必要もあります コンプリケーションには さまざまな種類がありますが そのほとんどはテキスト 画像 記号の 3要素で構成されます テキストはVoiceOverで 自動的にピックアップされますが テキストに略語が ある場合は 必ず非省略バージョンの アクセシビリティラベルを 追加してください ここでは省略形 "Wednesday Mar 9"を完全な "Wednesday, March 9th"に 拡張します 画像ベースのコンプリケーションも 非常に一般的です ここでもアクセシビリティラベルを 提供してください そうでなければ イメージ名が使われます "月"は "月のリアルタイムの眺め 第3四半期"ほど 説明的ではありません SF Symbolsなどの 特定の図像には "ドロップ フィル"などの デフォルトの アクセシビリティラベルが 付いている場合がありますが シンボルに付いている ラベルが わかりやすいもので あることを確認してください "Water in three days"は 私にとってとてもわかりやすいです 通知については Appがユーザーに たくさんの情報を 送信する方法です したがって一部の通知は 非常に単純ですが 動的な通知には 複雑なビューが 含まれる場合があるため Appに提供したものと同じ アクセシビリティサポートが 必要になります 次はヴィラータが 運動障害のための アクセシビリティに ついて説明します ヴィラータ ダニエル ありがとう ヴィラータです アクセシビリティチームの エンジニアです Apple Watchの運動障害 アクセシビリティサポートに ついてお話しできることを 大変嬉しく思います しかしまずは 新機能の概要を 簡単に説明します 画面に触れず手の ジェスチャーのみで Watchの盤面から コントロールセンターに 移動してボタンを オンにできます とても簡単です 今年 Apple Watchに AssitiveTouchを お届けできることが とても嬉しいです AssitiveTouchでは Apple Watch がオンに なっている手のみで タッチすることなく Apple Watchを フル活用できます ユーザーはハンド ジェスチャーや 手の動きで画面の カーソルを動かせます メニューを表示して 画面の内容に基づいて 追加機能にアクセスできます 一部の人にとっては これがApple Watchを 操作する唯一の方法と なる場合があります 手足の欠損や 手や腕の機能の喪失など 運動障害のある人も AssitiveTouchを使えば Apple Watchでの操作を 制御・実行できる オプションを追加できます それでは AssitiveTouchの 使用例を見てみましょう AssitiveTouchの 主な操作方法は ハンドジェスチャーです ユーザーは拳を握る (タップ)や 2回拳を握っての アクションメニュー表示 次のボタンに 移動するピンチ 前のボタンに戻る ダブルピンチなどの ジェスチャーを実行できます ハンドジェスチャーが 使えない人のための 代用が手の動きです 手首を傾けると 画面のポインタを動かして UIボタンを 操作できます iOSのAssitiveTouchと 同様にDwell Controlを 使うとポインタを ボタンに置いて 一定時間操作ができます
それではAssitiveTouchを 詳しく見ていきましょう AssitiveTouchの 主な機能は2つです カーソルと アクションメニューです AssitiveTouchを オンにすると 画面にカーソルが 表示されます カーソルは左上から 右下に向かって 画面上の各ボタン 1つずつにフォーカスを当てます カーソルでボタンが 高輝度表示されると やり取りが可能になります 選択したボタンに対して 他のアクションを 実行するには アクションメニューを 表示してシステム/カスタム アクションを実行します AssitiveTouchアクション メニューには Digital Crownの押下 スクロールナビゲーション ジェスチャ操作など デバイスの制御を可能にする デフォルトのシステム アクションが 用意されています このメニューにカスタム アクションの追加もできます さてAssitiveTouchの 仕組みを理解しました Appでのサポート方法を 見てみましょう 次のトピックに移ります まず選択可能なボタンが どのようにビューに 表示されるか またその要素を どう変更できるかです 次にカーソルフレームと フレームサイズの 変更方法を見ます 最後にアクションメニューの カスタマイズ方法の説明です 選択可能なボタンから 始めましょう この表は AssitiveTouchで 選択可能なボタンの概要です 選択可能なのは ユーザーの操作に応答する 対話型ボタンのみです SwiftUIにはユーザーの 操作を処理するための コントロール要素が 組み込まれています これらの要素は対話型での 選択が可能です ビューにボタン トグルまたは NavigationLinkが あれば これらの要素はデフォルトで フォーカスされます アクション可能な要素は アクションがあるか対話型として 定義されているため AssistiveTouchで選択可能です テキスト要素はタップ ジェスチャアクションの 適用で対話的に 選択可能になります AccessibilityActionを 要素に 追加することもできます ボタンや調整可能など 実用的特性を持つ 要素を定義すると 対話型として扱われ AssitiveTouch によって 選択されます 選択できない ボタンも一部あります AssitiveTouchでは ラベルやユーザー操作に 応答しないテキストなどの 静的要素は 選択できません ユーザー操作が無効な ボタンも選択できません 例を見てみましょう ここにはラベルヘッダや ドリンク情報を含む テキスト Accept および Cancel ボタンがあります 選択可能なのは 2つのボタンのみです ラベルとテキストは 選択できません このビューのコードを 見てみましょう メインVStackにタップ ジェスチャが付いています このビューを タップすると ドリンクの詳細ビューを 表示できるのです ただし明示的に 宣言しない限りVStack内の 静的要素は 選択可能になりません ここで選択できるのは AcceptボタンとCancelボタンのみです ではビュータップで ドリンクの詳細を見られることを ユーザはどのように 知るのでしょうか より良いユーザー体験を 提供するために ドリンク情報テキスト 要素を強調表示して それが対話可能で タップできることを示します そうするには accessibilityRespondsToUser 対話型修飾子に "true"を設定します 修飾子適用後 このビューには ドリンク情報テキスト Accept および Cancelボタンの 選択可能な要素が3つあります それではAssitiveTouch カーソルフレームを見ていきます ご覧のように AssitiveTouchでは 要素が厄介なのかアクションを 実行できるかを 強調するためにフォーカスされた エレメントが重要です AssitiveTouchカーソル フレームは 要素のタップ可能領域と同じです タップ可能な領域が 小さい要素は 小さなカーソルフレームがあり 内側のコンテンツを クリップする場合があります 余白を追加し境界線を オブジェクトの形状に 一致させてクリップを 防止することで このような作業を よりクリーンにできます この例では 省略記号の画像のある NavigationLinkが AssitiveTouchカーソルの 小さな円で 強調表示されています これを改善するには タップ可能領域のサイズを 大きくします これでAssitiveTouchカーソル フレームも変更されます contentShape修飾子への パスを指定することで タップ可能な領域と要素の カーソルフレームを 変更できます このNavigationLinkでは タップ可能な領域の形を 要素のサイズの半分の 円に設定します その結果AssitiveTouch カーソルフレームが大きくなり 画面上での表示が はるかに簡単になります それではAssitiveTouchアクション メニューを見てみましょう このメニューでは アクションメニューリスト に表示する デフォルトのシステムアクションと カスタムアクションが ビューに表示されます フォーカス要素に カスタムアクションがあれば それらが優先順位付けされ リストの先頭に表示されるので 操作しやすくなります エレメントが調整可能な場合は 減分/増分アクションが 表示されます アクセシビリティグループ要素に カスタムアクションがある場合は グループ要素にフォーカスすると それらのアクションが表示されます VoiceOverのカスタム アクションを 要素にすでに追加している場合は これで完了です これらのアクションは AssitiveTouchアクション メニューにも自動的に 表示されます カスタムアクションは アイコンとして表示されます デフォルトのイメージは カスタムアクション名の 最初の文字になります カスタムアクションアイコンの イメージを提供する場合は accessibilityAction修飾子に イメージ付きのラベルを 追加できます セッションはこれで終わりです これでApple Watchの アクセシビリティ機能の 理解が深まりました AppにDynamic Type VoiceOver、AssitiveTouchの サポートを必ず追加してください これらAPIで watchOS Appに誰もが アクセスできるようにするツールが 用意されています お時間ありがとうございました 残りのWWDCもお楽しみください ♪
-
-
4:48 - Dynamic Type for PlantView
struct PlantView: View { @Binding var plant: Plant var body: some View { VStack(alignment: .leading) { Text(plant.name) .font(.title3) HStack() { PlantImage(plant: plant) PlantTaskList(plant: $plant) } PlantTaskButtons(plant: $plant) } } }
-
5:00 - Line limits for PlantTaskLabel
struct PlantTaskLabel: View { let task: PlantTask @Binding var plant: Plant var body: some View { HStack { Image(systemName: task.systemImageName) .imageScale(.small) Text(plant.stringForTask(task: task)) } .lineLimit(3) .font(.caption2) } }
-
5:48 - Alternate layouts for PlantContainerView
struct PlantContainerView: View { @Environment(\.sizeCategory) var sizeCategory @Binding var plant: Plant var body: some View { if sizeCategory < .extraExtraLarge { PlantViewHorizontal(plant: $plant) } else { PlantViewVertical(plant: $plant) } } }
-
8:56 - Element grouping for PlantCellView
struct PlantCellView: View { @EnvironmentObject var plantData: PlantData var plant: Plant var plantIndex: Int { plantData.plants.firstIndex(where: { $0.id == plant.id })! } var body: some View { NavigationLink(destination: PlantEditView(plant: plant).environmentObject(plantData)) { PlantContainerView(plant: $plantData.plants[plantIndex]) .padding() } } }
-
9:38 - Accessibility labels for PlantTaskLabel
struct PlantTaskLabel: View { let task: PlantTask @Binding var plant: Plant var body: some View { HStack { Image(systemName: task.systemImageName) .imageScale(.small) Text(plant.stringForTask(task: task)) .accessibilityLabel(plant.accessibilityStringForTask(task: task)) } .lineLimit(3) .font(.caption2) } }
-
10:03 - Accessibility labels for PlantButton
struct PlantButton: View { let task: PlantTask let action: () -> Void @State private var isTapped: Bool = false var body: some View { Button(action: { self.isTapped.toggle() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.isTapped.toggle() } action() }) { Image(systemName: task.systemImageFillName) .foregroundColor(task.color) .scaleEffect(isTapped ? 1.5 : 1) .animation(nil, value: 0) .rotationEffect(.degrees(isTapped ? 360 : 0)) .animation(.spring(), value: 0) .imageScale(.large) } .buttonStyle(BorderedButtonStyle()) .accessibilityLabel("Log \(task.name)") } }
-
11:07 - Custom control accessibility for PlantTaskFrequency
struct PlantTaskFrequency: View { let task: PlantTask @Binding var plant: Plant let increment: () -> Void let decrement: () -> Void var value: Int { switch task { case .water: return plant.wateringFrequency case .fertilize: return plant.fertilizingFrequency default: return 0 } } var body: some View { Section(header: Text("\(task.name) frequency in days"), content: { CustomCounter(value: value, increment: increment, decrement: decrement) .accessibilityElement() .accessibilityAdjustableAction { direction in switch direction { case .increment: increment() case .decrement: decrement() default: break } } .accessibilityLabel("\(task.name) frequency") .accessibilityValue("\(value) days") }) } }
-
19:50 - Make static element focusable
struct FreeDrinkView: View { @State var didCancel = false @State var didAccept = false @State var showDetail = false var body: some View { VStack(spacing:10) { FreeDrinkTitleView() FreeDrinkInfoView() .accessibilityRespondsToUserInteraction(true) HStack { CancelButton(buttonTapped: $didCancel) AcceptButton(buttonTapped: $didAccept) } } .onTapGesture { showDetail.toggle() } .sheet(isPresented: $showDetail, onDismiss: dismiss) { DrinkDetailModalView() } } }
-
21:12 - AssistiveTouch cursor frame
struct DrinkView: View { var currentDrink:DrinkInfo var body: some View { HStack(alignment: .firstTextBaseline) { DrinkInfoView(drink:currentDrink) Spacer() NavigationLink(destination: EditView()) { Image(systemName: "ellipsis") .symbolVariant(.circle) } .contentShape(Circle().scale(1.5)) } } }
-
22:48 - AssistiveTouch Action Menu
PlantContainerView(plant: plant) .padding() .accessibilityElement(children: .combine) .accessibilityAction { // Edit action } label: { Label("Edit", systemImage: "ellipsis.circle") }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。