WWDC Quick Look 💓 By SwiftGGTeam
カスタムコントロールのアクセシビリティを磨く

カスタムコントロールのアクセシビリティを磨く

元の動画を見る

ハイライト

Appleはカスタムコントロール向けに、調整可能trait、穿透ジェスチャ、カスタム操作、直接タッチを含む包括的なアクセシビリティAPIを提供しています。これにより、支援技術に依存するユーザーも複雑なインタラクティブコントロールを完全に使えます。

主要内容

カスタムUIコントロールを作ること自体は難しくありません。難しいのは、それをすべてのユーザーが使えるようにすることです。

たとえば、見た目の良いコーヒー調整コントロールを作ったとします。指を上下に滑らせるとコーヒー量を調整できます。視力のあるユーザーなら一目で理解します。塗りつぶしの高さがコーヒー量を表し、ジェスチャはドラッグです。脳は形、位置、期待される動作を一瞬で処理します。

では、見えないユーザーはどうでしょう。トラック、ハンドル、位置といった視覚情報はすべて存在しません。

Apple内蔵のスクリーンリーダーであるVoiceOverは、これらの情報を音声で伝えます。標準のSliderコントロールなら、たとえば「Brightness, 50%, adjustable. Swipe up or down with one finger to adjust the value.」と読み上げます。ユーザーは、それが何で、現在値がいくつで、どう操作すればよいかを理解できます。

これがカスタムコントロールのアクセシビリティにおける4つの指針です。

  1. 目的が明確: コントロールが何をするものか
  2. 値が取得できる: 値があるなら、それを取得できること
  3. 操作が明確: ユーザーがどんな操作をできるか
  4. フィードバックが迅速: 操作後に何が変わったか

多くのカスタムコントロールは視覚的な操作だけを考慮し、支援技術を無視しています。その結果、VoiceOverやSwitch Controlに依存するユーザーはまったく使えません。

Appleはこの問題を解決するための包括的なAPI群を提供しています。単純な調整可能コントロールから、複雑な2次元イコライザー、複数ジェスチャを持つバーチャルペットまで、それぞれの場面に対応するツールがあります。

詳細内容

調整可能コントロールを本当に調整可能にする

(05:01)

CoffeeDispenserViewは、上下スワイプでコーヒー量を調整するカスタムスライダーです。しかしアクセシビリティ対応がないため、VoiceOverは「ボタン」といくつかの無関係な情報しか読み上げません。

最初のステップは、それをアクセシビリティ要素としてマークし、明確なラベルと値を与えることです。

struct CoffeeDispenserView: View {
    @State var coffee: Double = 0.0
    var body: some View {
        CoffeeSlider(value: coffee)
            .accessibilityElement()
            .accessibilityLabel("Coffee Dispenser")
            .accessibilityValue("\(Int(coffee)) ounces")
            .accessibilityAddTraits(.adjustable)
            .accessibilityAdjustableAction { direction in
                switch direction {
                case .increment:
                    increaseCoffeeAmount()
                case .decrement:
                    decreaseCoffeeAmount()
                }
            }
    }
}

ポイント:

  • .accessibilityElement()は、これが独立したアクセシビリティ要素であることを示します
  • .accessibilityLabelは、それが何であるかをVoiceOverに伝えます
  • .accessibilityValueは現在値を公開します
  • .adjustable traitは、このコントロールが調整可能であることを示します
  • .accessibilityAdjustableActionは、上下スワイプで何をするかを定義します

これでVoiceOverは「Coffee Dispenser, 6 ounces, adjustable. Swipe up or down with one finger to adjust the value.」と読み上げられます。

精密制御には穿透ジェスチャを使う

(07:05)

標準のスワイプでは、たとえば1オンスずつの固定ステップでしか調整できません。しかし、0.5オンス単位の精度が必要なユーザーもいます。

VoiceOverにはpassthrough gesture(穿透ジェスチャ)という組み込み機能があります。ダブルタップして押さえ続けると、タッチイベントがコントロールへ直接渡されます。

struct CoffeeDispenserView: View {
    @State var coffee: Double = 0.0

    var body: some View {
        CoffeeSlider(value: coffee)
            .accessibilityActivationPoint(
                UnitPoint(x: 0.5, y: 1 - coffee)
            )
    }
}

ポイント:

  • .accessibilityActivationPointはジェスチャの開始点を設定します
  • x: 0.5は水平方向中央、y: 1 - coffeeは開始点を現在のコーヒー量に追従させます
  • これにより、ユーザーの指はどの方向にも調整する余地を持てます

穿透ジェスチャにはフィードバックも必要です。値が変わるたびに毎回読み上げると、うるさくなりすぎます。

struct CoffeeDispenserView: View {
    @State var coffee: Double = 0.0

    var body: some View {
        CoffeeSlider(value: coffee)
            .onChange(of: coffee) { _, newValue in
                if sufficientTimeSinceLastAnnouncement() && valueHasChanged() {
                    cacheLastSpokenValue(newValue)
                    AccessibilityNotification
                        .Announcement(newValue)
                        .post()
                }
            }
    }
}

ポイント:

  • 前回の読み上げから0.3秒以上経過したか確認します
  • 値が本当に変わったか確認します
  • .post()でアクセシビリティ通知を送ります

ユーザーがダブルタップして押さえた後、指を動かすと「6.4 ounces… 8.4 ounces… 9.5 ounces」と聞こえます。

2次元コントロールにはカスタム操作を使う

(10:13)

iOSの背景音にあるイコライザーは2次元コントロールで、周波数と振幅の2軸を同時に動かせます。標準の.adjustable traitは1方向の増減しか扱えないため不十分です。

ここではcustom actions(カスタム操作)が必要です。

struct EqualizerView: View {
    var body: some View {
        EqualizerPad()
            .accessibilityActions("Move Up") {
                increaseY(by: 10)
            }
            .accessibilityActions("Move Right") {
                increaseX(by: 10)
            }
            .accessibilityActions("Move Down") {
                decreaseY(by: 10)
            }
            .accessibilityActions("Move Left") {
                decreaseX(by: 10)
            }
    }
}

ポイント:

  • .accessibilityActionsは複数追加でき、それぞれが名前とクロージャを持ちます
  • 4方向がそれぞれX軸またはY軸を操作します
  • VoiceOverは利用可能な操作をすべて読み上げ、ユーザーは上下スワイプで選択し、ダブルタップで実行します

操作中、VoiceOverは「Move down. Move up. Move right. Frequency 0, amplitude 40.」と読み上げます。

複数ジェスチャのコントロールには直接タッチを使う

(12:47)

バーチャルペットコントロールは3つのジェスチャに対応します。撫でると喉を鳴らし、タップすると鳴き、ピンチすると威嚇します。ユーザーが複数の動作を繰り返し実行したい場合があるため、穿透ジェスチャだけでは足りません。

直接タッチAPI(Direct Touch)がより良い選択です。

struct VirtualCat: View {
    var cat: CatModel
    var body: some View {
        InteractiveCatSurface()
            .accessibilityLabel("Virtual Cat")
            .accessibilityValue(cat.currentReaction.description)
            .accessibilityDirectTouch([.requiresActivation])
    }
}

ポイント:

  • .accessibilityDirectTouchは、その領域が直接タッチをサポートすることを示します
  • .requiresActivationにより、誤操作を避けるため有効化にはダブルタップが必要になります
  • 直接タッチはフォーカスが他の要素へ移るまで有効なままです
  • 直接タッチ中、VoiceOverは操作方法を音声で案内します

.silentOnTouchというオプションもあります。コントロール自身が音声フィードバックを提供する場合に使い、VoiceOverの音声とコントロール音声が重ならないようにします。

重要ポイント

ゲームコントローラーシミュレーターにアクセシビリティ対応を追加する

作るもの: 方向キーとA/Bボタンをカスタム操作でサポートし、Switch Controlユーザーもモバイルゲームを遊べるようにします。

作る価値: 多くのモバイルゲームは複雑なジェスチャ操作に依存しており、Switch Controlユーザーは参加できません。カスタム操作は各ゲーム動作をシステムが認識できる操作項目へ対応付け、ユーザー層を広げます。

始め方: ゲームコントロールに.accessibilityActionsを追加し、各方向キーとボタンに独立した操作名とクロージャを定義します。VoiceOverユーザーは上下スワイプで選択し、ダブルタップで実行できます。

音楽ミキサーの2次元イコライザーをアクセシブルにする

作るもの: 2次元イコライザーは周波数と振幅の2軸を同時に移動できます。custom actionsで周波数と振幅を個別に調整できるようにします。

作る価値: 標準の.adjustable traitは1方向の増減しかサポートしないため、2次元コントロールにはより柔軟な操作方式が必要です。カスタム操作ならVoiceOverユーザーも各次元を精密に制御できます。

始め方: イコライザービューに複数の.accessibilityActionsを追加し、それぞれを「上へ移動」「下へ移動」「左へ移動」「右へ移動」と名付け、各操作で対応する座標値を調整します。

お絵描きアプリで直接タッチモードを有効にする

作るもの: 直接タッチAPIと.silentOnTouchを組み合わせ、VoiceOverユーザーが指で直接描けるようにし、ツール音が読み上げに遮られないようにします。

作る価値: 絵を描くことは視覚芸術ですが、視覚障害のあるユーザーも触覚や音声フィードバックを通じて創作できます。直接タッチモードでは指の触れた動きがキャンバスに直接作用し、.silentOnTouchはVoiceOver音声と描画音が重なるのを避けます。

始め方: キャンバスビューに.accessibilityDirectTouch([.requiresActivation, .silentOnTouch])を追加します。.requiresActivationで誤操作を防ぎ、.silentOnTouchでコントロール音声を優先的に再生します。

フィットネス強度調整をアクセシブルにする

作るもの: カスタムスライダーに調整可能traitと穿透ジェスチャを追加し、粗い調整から細かな調整まで対応します。

作る価値: フィットネスアプリの強度調整コントロールは、塗りつぶし高さのような視覚フィードバックに依存しがちで、見えないユーザーは現在値を把握できません。調整可能traitは現在値を公開し、穿透ジェスチャは精密な微調整を支えます。

始め方: .accessibilityElement().accessibilityLabel.accessibilityValue.accessibilityAddTraits(.adjustable)を追加し、.accessibilityAdjustableActionで増減を処理し、.accessibilityActivationPointで穿透ジェスチャをサポートします。

バーチャルペットのインタラクションを直接タッチに対応させる

作るもの: バーチャルペットコントロールは撫でる、タップ、ピンチなど複数のジェスチャに対応します。直接タッチモードを使い、VoiceOverユーザーも完全な体験を得られるようにします。

作る価値: おもちゃ系アプリの楽しさは多様なインタラクションにあります。単一タップだけの対応では体験の大部分が失われます。直接タッチモードは有効化後もタッチイベントを継続受信し、複雑なジェスチャの組み合わせに対応できます。

始め方: インタラクティブ領域に.accessibilityDirectTouch([.requiresActivation])を追加します。有効化中、VoiceOverは利用可能な操作を音声案内し、ユーザーは各種ジェスチャを直接実行できます。

関連セッション

  • 219 Accessibility Reading — 読書コンテンツのアクセシビリティベストプラクティスを紹介します
  • 221 tvOS Dynamic Type — tvOSで文字サイズを自動調整する実装です
  • 262 SwiftUI — アクセシビリティ関連の更新を含むSwiftUIの新機能です
  • 269 SwiftUI — フレームワークレベルのアクセシビリティサポートです
  • 290 Naming — アクセシビリティラベルを明確に保つための命名規約です

コメント

GitHub Issues · utterances