View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

WWDC20に戻る

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • カスタムローターを使ったVoiceOverの効率性

    カスタムローターを統合し、あなたのApp内での複雑な状況でユーザーがVoiceOverを使いナビゲーションを受けられるようになる方法をお見せします。複雑に入り組んだインターフェースであっても、カスタムローターがあればユーザーは探りながら進むことができ、VoiceOver頼りの人に対してもローターでナビゲーションを改善できます。 このセッションを最大限に活用するためには、アクセシビリティとiOS、iPadOSのVoiceOverアクセシビリティAPIの全般的な理念に親しんでおくことが望ましいです。概略については、"Making Apps More Accessible with Custom Actions"をご覧ください。

    リソース

    • Accessibility for UIKit
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC23

    • アクセシブルな空間体験の実現

    WWDC21

    • SwiftUIのアクセシビリティ: 基礎を超えて

    WWDC19

    • カスタムアクションでAppのアクセシビリティを高める
  • このビデオを検索

    こんにちは WWDCへようこそ

    アレックス・ヴァルツァックです “カスタムローターを使った VoiceOverの効率性” VoiceOverにカスタムローターを 追加する方法を紹介します VoiceOverは 画面読み上げ機能です 画面が見えない人の デバイス操作に役立ちます 画面をタップして VoiceOverを起動すると 簡単なジェスチャで UIをナビゲートします

    画面が見えない人が ローター使って 操作する方法を いくつか紹介します ダイヤルを回すように 画面上で指を回転すると ローターが起動します 上下にスワイプすることで 前の項目や 次の項目に移動できます カスタムローターをアプリケーションに 追加すれば ユーザー体験が変わります 複雑な操作法が簡略化され アプリケーション内の検索も 上下にフリックするように簡単です いくつかの例を紹介します 複雑な操作を カスタムローターで簡単にします まずは カスタムローターなしの ユーザー体験を見てください 地図アプリケーションで現在地とApple Store 近くの公園を表示します VoiceOverを起動すると UIのレイアウト方向に従って アプリケーションのビューが動くのが分かります アップル ベイストリート サンフランシスコ湾 ベイブリッジ アルカトラズ島

    カスタムローターなしのVoiceOverで アプリケーションを使った例です ご覧ください カーソルがApple Storeと公園の間だけでなく 橋や他の地点の間も動きます VoiceOverユーザーは すべての項目を順番に移動しなくてはなりません 一方 画面を見ればアイコンとその色から 特定の項目に目が行きます VoiceOverユーザーが 心地よくアプリケーションを使うには? まずインターフェース上で 視覚的に注意を引く項目を把握します 今回はApple Storeと公園ですね

    次に カテゴリー別に グループ分けします カテゴリー内で項目を探索する カスタムローターを作ります やってみましょう Apple Storeと公園のローターを 1つずつ実装します すると各ローターの項目を ユーザーからの距離で並べ替え

    その距離をアクセシビリティに 含めることができます これで 一番近くのApple Storeが すぐに見つかります

    一番近くの公園も同様です

    画面を見て 自分に最も近い場所に 目が行くのと同じですね

    2つのローターを 実装した例をご覧ください まずは完成後のApple Storeローターです Apple Stores

    Apple チェスナットストリート 0.9マイル Apple ユニオンスクエア 1.8マイル Apple ストーンズタウン 4.6マイル 次に公園のローターです 公園

    アラモ・スクエア 0.8マイル コロナ・ハイツ・パーク 1.5マイル ヒッピー・ヒル 1.5マイル ユーザーは各カスタムローターの 並べ替えた場所を移動するので Apple Storeでも公園でも 一番近い場所がすぐに見つかります カスタムローターはすべてのユーザーに 同様の使いやすさを提供します 特にこの地図のように複雑な操作に有効です アプリケーションにローター追加する方法は? accessibilityCustomRotorプロパティで カスタムローターを探します

    今回はローターが2つ必要です Apple Store用と公園用に 2つのカスタムローターを設定します

    各ローターに 同じ地図の注釈をフィルタリングするので 2のローターに 1つのメソッドを実装するだけです

    新たなUIAccessibilityCustomRotorを返す メソッドを作成します カスタムローターは 最小限の追加作業で実装できます ローカライズした名前で UIAccessibilityCustomRotorを初期化し クロージャで基本ロジックを実行 するとVoiceOverが UIAccessibilityCustomRotorと返します

    ブロック引数から現在のローターの項目を抽出し 移動可能なリストを用意します searchDirectionは上下のフリップで 前に行くか次に行くかを知らせます この情報を使って 可能性リストの インデックスを増減できます

    最初か最後にいることを ユーザーに示すため nil値に戻し VoiceOverは目標物に焦点を当てたままです それ以外は 前後の項目を targetElementとして 新たなUIAccessibilityCustomRotorの 結果に戻して終了します 開発者としてカスタムローターAPIを 活用するために覚えるべきことは クロージャを実装し カスタムローターを accessibilityCustomRotorsに追加することです カスタムローターは アプリケーション内の要素グループに作用します アクセシビリティの改善法をもっと学ぶなら カスタムアクションの相互作用について このトークをチェックしてください 地図上で関連する要素を見つける機能は 既に説明しました 次はカスタムローターをテキストで使う方法です

    場所をタップしてパンフレットを表示します 今回はゴールデンゲートパークの パンフレットです やることがいっぱいです テキストを自動で表示する ラインローターで 1文ずつ見ていきます ライン ゴールデンゲートパーク 訪問計画 オランダ風車 3月に満開のチューリップを見に行く 公園の東端にある背の高い建物 イースト・メドー ピクニック場はメンテナンスで閉鎖中 ペット連れの家族に最適な場所 一部のコンテンツは聞けましたが 最初の警報までにかかった時間は? イースト・メドーの警報を聞くまでに その前のすべてを聞く必要がありました まだ聞いてない警報もあります つまり すべての警報を聞くには すべての情報を慎重に聞く必要があります 実際はカスタムローターを実装して 警報のみを表示することができるので 警報のアイコンに気付く速さで 重要な情報が聞けます 警報ローターを見てみましょう 警報 イースト・メドー ピクニック場はメンテナンスで閉鎖中 オーク・ウッドランド山道 ぬかるんだ状態 オーシャン・ビーチ 濃霧と強風に注意 予想どおり 警報ローターは 警報のみを表示するので 公園に行く計画を 効率よく立てられます

    textViewには customRotorが for alertsとして1つだけあります textViewの accessibilityCustomRotorsに 含むローターは1つだけです 新しいローターを返すメソッドを実装します 公園のようなロケーションのタイプの代わりに ローターに置きたい テキスト属性のタイプで渡します この場合 それは警報属性です このメソッドの構文は 地図の例と似ています ここでもローカライズした名前で UIAccessibilityCustomRotorを初期化し VoiceOverの移動先の項目を返す クロージャを実装します

    すべての警報は textViewの 属性付きテキストで見つけることができます テキスト内のローター項目の範囲から 警報の範囲を特定しますが ユーザーの進みたい方向で異なります ちょうどこの箇所です カスタム警報属性を検索するテキストの範囲と ユーザーのジェスチャに基づいて 検索する方向を決めます 警報の一致で 停止できると分かります その時点で UIAccessibilityCustomRotorの結果を返し 新たなtargetRangeとして渡します またはtargetRangeを nil値にして 最初か最後にいることを示します 注意すべきは targetRangeは UITextRangeであり targetElementは UITextInputプロトコルに適合することです 取り込むには かなりの情報量ですがアプリ ケーションにカスタムローターを実装すると ローターの初期化に使ったブロックから 前後のカスタムローターの結果が得られます

    accessibilityCustomRotorsを使えば フィルタリングして特定の項目に絞れます 前半はカスタムローターを使った 地図アプリケーションの改善法でした 次にテキストベースのコンテンツとの 関わり方と強化法を説明しました テキストコンテンツのアクセシビリティを さらに学ぶなら このセッションをご覧ください

    カスタムローターや VoiceOverについて学んだ知識で アプリケーションのアクセシビリティを 検証してください 詳細はアプリケーションのテストに関する 過去のセッションをご覧ください VoiceOverの効率を上げるには 次のことが必要です まずインターフェースの 視覚的に最も複雑な領域の特定です VoiceOverを使った場合 使わないのと同じくらい簡単か確かめます 簡単でない場合は 画面が見えてないのと同じです そこでカスタムローターをお勧めします あなたがデザインしたアプリケーションが 誰にでもうまく機能するか確かめてください WWDC 2020をご覧いただき ありがとうございました

    • 4:04 - mapView.accessibilityCustomRotors = [customRotor(for: .stores), customRotor(for: .parks)]

      mapView.accessibilityCustomRotors = [customRotor(for: .stores), customRotor(for: .parks)]
    • 4:31 - map rotor 1

      // Custom map rotors
      
      func customRotor(for poiType: POI) -> UIAccessibilityCustomRotor {
          UIAccessibilityCustomRotor(name: poiType.rotorName) { [unowned self] predicate in
      
              return UIAccessibilityCustomRotorItemResult(    )
          }
      }
    • 4:56 - map rotor 2

      // 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 }
              return UIAccessibilityCustomRotorItemResult(    )
      
          }
      }
    • 5:04 - map rotor 3

      // 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
              }
              return UIAccessibilityCustomRotorItemResult(    )
      
          }
      }
    • 5:17 - Maps rotor 4

      // 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)
          }
      }
    • 8:07 - Text rotor 1

      // 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 = 
              guard let currentRange =    else { return nil }
      
              switch predicate.searchDirection {   }
      
              return UIAccessibilityCustomRotorItemResult(targetElement: self,
                                                          targetRange: targetRange)
          }
      }
    • 8:20 - Text rotor 2

      // 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 {   }
      
              return UIAccessibilityCustomRotorItemResult(targetElement: self,
                                                          targetRange: targetRange)
          }
      }
    • 8:37 - Text rotor 3

      // 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 = 
              guard let currentRange =    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 = []
              }
             
              return UIAccessibilityCustomRotorItemResult(targetElement: self,
                                                          targetRange: targetRange)
          }
      }
    • 9:06 - Text rotor 4 (end)

      // 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 =
              guard let currentRange =    else { return nil }
              let searchRange: NSRange, searchOptions: NSAttributedString.EnumerationOptions
              switch predicate.searchDirection {   }
              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)
          }
      }

Developer Footer

  • ビデオ
  • WWDC20
  • カスタムローターを使ったVoiceOverの効率性
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン