WWDC Quick Look 💓 By SwiftGGTeam
VoiceOver efficiency with custom rotors

VoiceOver efficiency with custom rotors

元の動画を見る

ハイライト

VoiceOver のローターを使用すると、ユーザーは上下を使用して選択した次元内でジャンプできます。カスタム Rotor を使用すると、アプリはマップの注釈、テキスト警告、その他のコンテンツを独自のカテゴリに従ってスクリーン リーダー ユーザーに公開できます。


主要内容

VoiceOver ユーザーは画面が見えない場合、タッチを通じて現在地を聞き取り、ジェスチャを使用してインターフェイス要素間を移動します。ローターは、このプロセスの効率的なエントリ ポイントです。ユーザーは 2 本の指を使用して回転して寸法を選択し、上下にスライドして前または次の項目に移動します。システムは文字、単語、線などの共通のディメンションをすでに提供していますが、複雑なアプリには独自のコンテンツ分類があることがよくあります。 (00:29)

セッションの最初の例はマップです。このマップには、Apple Store、公園、橋、その他の名所も掲載されています。画面を見ている人は、アイコンの色に基づいて特定の種類の場所に焦点を合わせることができます。 VoiceOver ユーザーは、デフォルトで、インターフェイスの順序ですべての注釈を 1 つずつ聞くようになります。 Apple のアプローチは、視覚的に最も重要なカテゴリを見つけて、Apple Store と公園ごとにカスタム ローターを構築し、ユーザーの距離に基づいて分類することです。 (01:19)

2 番目の例はテキストのパンフレットです。内蔵のライン ローターは行ごとに読み取ることができますが、駐車アラートを見つけるために、ユーザーはまず前の通常のコンテンツを聞く必要があります。アラート ローターをカスタマイズして、アラート属性を持つテキスト範囲を抽出すると、ユーザーは上下にスワイプするときにのみこれらのアラート間を移動できるようになります。この例は、カスタム ローターがマップの注釈だけでなく、リッチ テキスト、フォーム、タイムライン、その他のコンテンツ中心のインターフェイスにも適していることを示しています。 (06:13)

このセッションの焦点は、各要素にラベルを追加するだけではなく、VoiceOver ユーザーにビジュアル ユーザーと同等の閲覧パスを提供することです。実装上、開発者は作成するだけで済みます。UIAccessibilityCustomRotor、クロージャの方向に従って次のものを返します。UIAccessibilityCustomRotorItemResult、ローターを関連するビューに配置します。accessibilityCustomRotors09:13

詳細

カスタム ローターをターゲット ビューにアタッチする

(04:04) VoiceOver がマップ ビューにフォーカスすると、このビューが読み取られます。accessibilityCustomRotors。マップの例には 2 つのローターが必要です。1 つは Apple Store を閲覧するため、もう 1 つは公園を閲覧するためです。どちらのローターも同じファクトリーメソッドを再利用し、POI タイプのみを入力として受け取ります。

mapView.accessibilityCustomRotors = [customRotor(for: .stores), customRotor(for: .parks)]

キーポイント:

  • accessibilityCustomRotorsコンテンツをホストするビューに属します。 VoiceOver がそのビューに到達するまで、ユーザーにはこれらのカスタマイズ オプションが表示されません。
  • アレイ内に複数のローターを配置できるため、地図、ギャラリー、ドキュメントなどの複数の閲覧次元を持つインターフェイスに適しています。 -customRotor(for:)入力はビジネス分類、セッションはそれぞれ Apple Store と公園です。

クロージャ内の前または次のターゲットを返します。

04:31UIAccessibilityCustomRotorローカライズされた名前で初期化され、クロージャを受け取ります。クロージャのパラメータには、現在のローター項目と検索方向が含まれます。マップの例では、まず現在の要素を次のように変換します。MKAnnotationView、同じラベル配列からターゲットの添字を計算します。

// Custom map rotors

func customRotor(for poiType: POI) -> UIAccessibilityCustomRotor {
    UIAccessibilityCustomRotor(name: poiType.rotorName) { [unowned self] predicate in
        let currentElement = predicate.currentItem.targetElement as? MKAnnotationView
        let annotations = self.annotationViews(for: poiType)
        let currentIndex = annotations.firstIndex { $0 == currentElement }
        let targetIndex: Int
        switch predicate.searchDirection {
        case .previous:
            targetIndex = (currentIndex ?? 1) - 1
        case .next:
            targetIndex = (currentIndex ?? -1) + 1
        }
        guard 0..<annotations.count ~= targetIndex else { return nil } // Reached boundary
        return UIAccessibilityCustomRotorItemResult(targetElement: annotations[targetIndex],
                                                    targetRange: nil)
    }
}

キーポイント:

  • predicate.currentItem.targetElementは、VoiceOver が現在留まっている要素であり、マップはそれが次であることを期待しています。MKAnnotationView
  • annotationViews(for:)現在のローターに対応する同じタイプのラベルを返します。たとえば、パークのみを返します。 -predicate.searchDirectionユーザーが上にスライドしたか下にスライドしたかを識別し、コードはそれに応じてインデックスをデクリメントまたはインクリメントします。
  • 範囲外の場合は戻りますnil, VoiceOver は現在の注目点に留まります。ターゲットに当たると戻るUIAccessibilityCustomRotorItemResult
  • マップ要素はテキスト範囲ではないため、targetRange合格nil

属性付きテキストをアラート ローターとして使用する

(08:07) すべてのアラート コピーをカスタムとしてマークするテキストの例NSAttributedString.Key。ロータークロージャは電流から動作しますUITextRangeまず上下にスライドして検索範囲と方向を決定し、この属性で次の範囲を検索します。

// Custom text rotor

func customRotor(for attribute: NSAttributedString.Key) -> UIAccessibilityCustomRotor {
    UIAccessibilityCustomRotor(name: attribute.rotorName) { [unowned self] predicate in
        var targetRange: UITextRange? // Goal: find the range of following `attribute`
        let beginningRange = self.textRange(from: self.beginningOfDocument,
                                            to: self.beginningOfDocument)
        guard let currentRange = predicate.currentItem.targetRange ?? beginningRange else {
            return nil
        }
        let searchRange: NSRange, searchOptions: NSAttributedString.EnumerationOptions
        switch predicate.searchDirection {
        case .previous:
            searchRange = self.rangeOfAttributedTextBefore(currentRange)
            searchOptions = [.reverse]
        case .next:
            searchRange = self.rangeOfAttributedTextAfter(currentRange)
            searchOptions = []
        }
        self.attributedText.enumerateAttribute(
            attribute, in: searchRange, options: searchOptions) { value, range, stop in
            guard value != nil else { return }
            targetRange = self.textRange(from: range)
            stop.pointee = true
        }
        return UIAccessibilityCustomRotorItemResult(targetElement: self,
                                                    targetRange: targetRange)
    }
}

キーポイント:

  • beginningRange初めてローターにアクセスするユーザーに、現在のアイテムを見逃さないように開始点を提供します。targetRange検索できません。 -targetRangeタイプは次のとおりである必要がありますUITextRange; 転写特別リマインダー、テキストローターtargetElement以下にも準拠する必要がありますUITextInput
  • .previous逆列挙を使用します。.next最初のヒットがユーザーが移動したい前または次のアラートになるように、前方列挙を使用します。 -enumerateAttribute空ではない属性を入力した後、NSRangeに変換するUITextRangeそして検索の続行を停止します。 ・アタリがないときtargetRange保つnil, VoiceOver は境界に到達したものとして扱います。

重要ポイント

  • 地図と店舗の検索ページにカテゴリ Rotor を追加。対処法: VoiceOver ユーザーが「店舗」、「駐車場」、「バスルーム」、「入口」などのカテゴリから選択して、そのカテゴリのみを参照できるようにします。実行する価値がある理由: セッション マップのデモは、ビジュアル ユーザーがフィルタリングにアイコンの色に依存しているのに対し、VoiceOver ユーザーは同等のナビゲーション ディメンションを必要としていることがわかります。開始方法: POI カテゴリごとにソートされたアクセシビリティ要素配列を維持し、対応するローターをマップ ビューに配置します。accessibilityCustomRotors

  • 長いテキストに対して警告または強調ローターを追加します。何をすべきか: プロトコール、サイトの説明、医療報告書、コース資料の警告、期限、エラー、またはアクション項目にマークを付けます。実行する価値がある理由: パンフレットのデモでは、アラート ローターを使用すると、ユーザーは「ピクニックエリア閉鎖」や気象警報を直接聞くことができます。開始方法: 属性付き文字列を使用してターゲット テキストを属性化し、ローター クロージャで属性を列挙して返します。targetRange

  • ビジュアル フィルターを VoiceOver ブラウジング ディメンションに変換します。対処法: 色、アイコン、位置、または密度の表現に依存するインターフェイス内のカテゴリを確認し、ローターをタスクの完了に実際に影響を与えるカテゴリにします。実行する価値がある理由: このトランスクリプトは、「視覚的に注目を集める」要素を特定し、それらをカテゴリに分類することを開発者に思い出させます。開始方法: デザインとアクセシビリティ テストを連携させて、価値の高いディメンションを 2 ~ 3 つリストアップし、最初にそれらを最も複雑な画面に実装します。

  • QA にローター パス テストを追加。対処法: VoiceOver をオンにし、複雑なページ エントリから開始し、ローターを使用して主要なタスクを完了します。実行する価値がある理由: セッションの最後では、開発者は視覚的に複雑な領域を特定し、VoiceOver が同様に簡単にコンテンツに到達できるかどうかを確認する必要があります。開始方法: マップ、リッチ テキスト、テーブル、または集計表ビューの手動チェックリストを作成し、各ローターの名前、順序、境界の動作を文書化します。

関連セッション

コメント

GitHub Issues · utterances