WWDC Quick Look 💓 By SwiftGGTeam
Focus on iPad keyboard navigation

Focus on iPad keyboard navigation

元の動画を見る

ハイライト

iPadOS 15 と Mac Catalyst では、UIKit にキーボード フォーカス ナビゲーションが導入されています。開発者は、リスト、コレクション ビュー、およびカスタム ビューを Tab、矢印キー、Return キーに応答させることができ、フォーカス グループを使用してタブのサイクル順序を制御できます。

主要内容

多くの iPad ユーザーはハードウェア キーボードを接続しています。以前は、アプリではユーザーがキーボードを使用してインターフェース内を移動することが求められていました。一般的なアプローチは、Tab、矢印キー、Return キーを単独で監視し、一連のナビゲーション ロジックを手書きすることでした。

この計画はすぐに複雑になる可能性があります。写真グリッド、サイドバー、検索ボックス、リスト列間の関係は自分で維持する必要があります。 Mac Catalyst は、スペースバーの選択、メニューのショートカット、およびシステム動作の競合も考慮します。

iPadOS 15 では、これを UIKit のフォーカス システムに任せます。 Tab キーはアプリの重要な領域間を移動し、矢印キーは現在の領域内を移動し、iPadOS では Return キーで項目を選択し、Mac Catalyst ではスペースバーで項目を選択します。

テキスト ボックス、テキスト ビュー、サイドバーでは、iOS 15 SDK にコンパイルされると、この動作が自動的に有効になります。コレクション ビュー、テーブル ビュー、およびカスタム ビューには、開発者による明示的なアクセスが必要です。

このセッションの要点は明らかです。キーボード フォーカスですべてのコントロールを選択できるようにしないでください。キーボード ナビゲーションは、テキスト入力、リスト、コレクション ビューなどの主要なコンテンツ領域を提供します。ボタン、セグメント化されたコントロール、スイッチなどのコントロールは、すでにフル キーボード アクセスの対象になっています。

「集中できますか?」から始めましょう。

フォーカス システムは、まず「この要素はフォーカスになれるか?」という質問に答えます。

使用したUIKitUIFocusItem(フォーカス項目) は、フォーカス可能なオブジェクトを表します。UIFocusEnvironment(フォーカス環境)は上下関係を表します。UIViewビュー自体にフォーカスでき、サブビューも含めることができるため、両方のプロトコルに準拠します。UIViewControllerそれ自体の目に見える外観がないため、フォーカス環境にのみ準拠します。

開発者が行う必要がある最初のステップは、どのコンテンツがキーボード ナビゲーションに値するかを UIKit に伝えることです。

フォーカスの外観はコンテンツに適合する必要があります

キーボードのフォーカスが表示されている必要があります。 Halo Effect は、Mac Catalyst によってデフォルトで提供および使用されます。 iPadOS では、次のことができますUIFocusHaloEffect明示的に設定されます。

完全に不透明な画像には、ハローが適しています。通常のセルの場合、フォーカス状態は選択された強調表示に似ています。 iOS 14 で導入された背景設定とコンテンツ設定により、このセルの強調表示の外観を自動的に取得できます。

システムスタイルが適切でない場合、開発者はそれをオフにすることができますfocusEffect、フォーカス更新コールバックで色、境界線、またはコンテンツの状態を自分で変更します。

タブループには構造情報が必要です

矢印キーは現在の領域内でのみ移動します。タブはエリア間の移動を担当します。

UIKit では、これらの領域をフォーカス グループと呼びます。検索ボックス、サイドバー リスト、および右コンテンツ リストをそれぞれ 3 つのフォーカス グループにすることができます。タブは、各フォーカス グループの主な項目間を移動します。

システムはビュー階層からフォーカス グループを推測します。カスタム コンテナ ビューは手動で宣言する必要がある場合がありますfocusGroupIdentifier, タブ オーダーがユーザーに表示されるレイアウトと一致するようにします。

キーボード イベントが応答チェーンに入ります

フォーカス システムとレスポンダー チェーンは iPadOS 15 で同期されます。フォーカスがセルに移動すると、UIKit はファーストレスポンダーをこのセル、またはファーストレスポンダーになることができる応答チェーン内の最初のオブジェクトに移動しようとします。

これにより変更が生じます。セルがキーボード コマンドを実装しており、そのコマンドがフォーカスされている場合、コマンドは最初にこのセルに送信されます。開発者は電話するときに注意する必要がありますbecomeFirstResponder、フォーカスの変化を逆に駆動するためです。

詳細

カスタム ビューをフォーカス アイテムにします (03:01)

canBecomeFocusedこれは、項目がフォーカス可能かどうかを判断するフォーカス システムの入り口です。それはですUIFocusItem読み取り専用属性。カスタムビューはこれをオーバーライドして返すことができますtrue

override var canBecomeFocused: Bool { true }

キーポイント:

  • override親クラスによって提供されるプロパティをオーバーライドすることを示します。
  • canBecomeFocusedフォーカスシステムの判定入口です。
  • Bool { true }このビューを常に中心にしてください。

UIKit のフォーカス システムは、次の 2 つのプロトコルに基づいています。

UIFocusItem
UIFocusEnvironment

キーポイント:

  • UIFocusItem注目できる項目を示します。
  • UIFocusEnvironmentフォーカス項目を含む階層環境を表します。
  • UIView両方のプロトコルに準拠します。
  • UIViewController一致するだけUIFocusEnvironment

コンテンツが Metal などのテクノロジを使用して描画されている場合は、これら 2 つのプロトコルを独自のオブジェクトに実装して、非 UIKit コンテンツをフォーカス システムに接続することもできます。

コレクション ビューとテーブル ビューをキーボード ナビゲーションに参加させます (04:00)

リストとグリッドは、最も一般的なキーボード ナビゲーション オブジェクトです。のUIKitUITableViewそしてUICollectionView便利なAPIを提供します。設定allowsFocus最後に、各セルをサブクラス化する必要はありません。

class MyViewController: UICollectionViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView.allowsFocus = true
    }
}

キーポイント:

  • MyViewController継承元UICollectionViewController
  • viewDidLoad()これは、コレクション ビュー コントローラーの読み込みが完了した後の構成エントリです。
  • super.viewDidLoad()親クラスの初期化処理を保持します。
  • self.collectionView.allowsFocus = trueコレクション ビューのセルにフォーカスを取得します。

サイドバーにあるallowsFocusデフォルトはtrue。通常のリストとグリッドはオンデマンドで開く必要があります。

一部のセルのみがキーボード ナビゲーションに参加する必要がある場合は、これをデリゲートで実装できます。canFocusItemAt

class MyCollectionViewDelegate: NSObject, UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView,
                canFocusItemAt indexPath: IndexPath) -> Bool {
        return true
    }
}

キーポイント:

  • MyCollectionViewDelegate成し遂げるUICollectionViewDelegate
  • collectionView(_:canFocusItemAt:)シングルの場合indexPathフォーカス権限を返します。
  • return trueこの位置を表すセルにフォーカスを合わせることができます。
  • ここでは、ビジネスルールに従って戻ることができますfalse、プレースホルダーや操作不能なアイテムのスキップなど。

これらのメソッドは影響を与えるだけで、オーバーライドしません。canBecomeFocused細胞の。セル自体が上書きされる場合canBecomeFocused、その判断が優先されます。

デバッグ時にLLDBで呼び出すことができますUIFocusDebugger.checkFocusability(for:)

po UIFocusDebugger.checkFocusability(for: item)

キーポイント:

  • poオブジェクトを出力する LLDB コマンドです。
  • UIFocusDebugger.checkFocusability(for:)フォーカスシステムで項目が選択できない理由を説明します。
  • itemデバッグしているビューまたはフォーカス項目を渡します。

Halo Effect を使用してフォーカスの外観を制御する (05:48)

Mac Catalyst のデフォルトでは、macOS のフォーカス リングと同様の Halo Effect が設定されています。 iPadOS でビューを提供できますfocusEffect特定UIFocusHaloEffect

let focusEffect = UIFocusHaloEffect(
    roundedRect: self.bounds,
    cornerRadius: self.layer.cornerRadius,
    curve: .continuous
)
self.focusEffect = focusEffect

キーポイント:

  • UIFocusHaloEffectハローフォーカス効果を作成します。
  • roundedRect: self.bounds現在のビューの境界の周囲にハローを描画します。
  • cornerRadius: self.layer.cornerRadiusハローの丸い角をビューの丸い角と一致させます。
  • curve: .continuous連続フィレット カーブを使用します。
  • self.focusEffect = focusEffect現在フォーカスされている項目に効果を適用します。

一部のビュー階層はより複雑です。たとえば、写真の上にバッジがある場合、ハローは写真の上でバッジの下にある必要があります。設定可能referenceViewそしてcontainerView

let focusEffect = UIFocusHaloEffect(
    roundedRect: self.bounds,
    cornerRadius: self.layer.cornerRadius,
    curve: .continuous
)

// make sure the effect is added right above the image view
focusEffect.referenceView = self.imageView

// make sure the effect is added to our scroll view
focusEffect.containerView = self.scrollView

self.focusEffect = focusEffect

キーポイント:

  • referenceViewビュー階層内のハローの相対位置を決定します。
  • self.imageView画像ビューの上にハローを描画します。
  • containerViewハローを追加する親ビューを決定します。
  • self.scrollViewこれにより、直接の親ビューがハロー コンテンツをクリッピングするのを防ぎます。
  • どちらのプロパティもオプションであり、システム推論結果が期待どおりでない場合にのみ設定されます。

カスタム フォーカスの外観 (07:43)

システム フォーカス スタイルはすべてのデザインをカバーできるわけではありません。外観を完全にカスタマイズするには、まず次のようにします。focusEffectに設定nil、 またdidUpdateFocus(in:with:)フォーカスの出入りの状態を処理します。

init(frame: CGRect) {
   super.init(frame: frame)
   self.focusEffect = nil
}

func didUpdateFocus(in context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
    if context.nextFocusedItem == self {
        // This view is focused. Customize its appearance.
    }
    else if context.previouslyFocusedItem == self {
        // This view was focused.
    }
}

キーポイント:

  • init(frame:)カスタムビューの初期化入口です。
  • super.init(frame: frame)親クラスの初期化を呼び出します。
  • self.focusEffect = nilシステム フォーカス スタイルをオフにします。
  • didUpdateFocus(in:withAnimationCoordinator:)フォーカスが変更されるときに呼び出されます。
  • context.nextFocusedItem == self現在のビューがフォーカスを取得していることを示します。
  • context.previouslyFocusedItem == self現在のビューがフォーカスを失ったばかりであることを示します。

このコールバックは、前のフォーカス項目と次のフォーカス項目の祖先環境に送信されます。親ビューとビュー コントローラーもそれを受け取ります。取り扱い時の確認nextFocusedItemまたはpreviouslyFocusedItemそれは現在の環境に関連していますか?

サイドバーの選択がフォーカスに従うようにします (09:08)

サイドバーには特別なエクスペリエンスがあります。フォーカスがどの項目に移動しても、選択された状態もそれに応じて移動します。 UIKitがそれを呼び出すselectionFollowsFocus

var selectionFollowsFocus: Bool

キーポイント:

  • selectionFollowsFocusフォーカスを移動したときに同時に選択範囲を変更するかどうかを制御します。
  • サイドバーなどのコンテキストを変更するリストに適しています。
  • ウィンドウをポップアップしたり、新しいページをプッシュしたり、破壊的なアクションをトリガーしたりするセルには適していません。

少数の例外を除いて、ほとんどのセルがフォーカスに従いたい場合は、デリゲート メソッドを使用してセルを 1 つずつ制御できます。

func collectionView(_ collectionView: UICollectionView, selectionFollowsFocusForItemAt indexPath: IndexPath) -> Bool {
    return self.action(for: indexPath).type != .showAlert
}

キーポイント:

  • selectionFollowsFocusForItemAt単一セルの選択戦略を返します。
  • self.action(for: indexPath)この位置に対応するアクションを取り出します。
  • .type != .showAlertフォーカスが移動したときにダイアログ ボックスを直接ポップアップ表示しないようにします。
  • コレクションビュー自体selectionFollowsFocusそれでも全体的な意図を表現します。

写真の「新しいアルバム」はその一例です。これを選択すると、アルバム名を入力するためのアラートがポップアップ表示されるため、直接選択をトリガーするフォーカス移動には適していません。

フォーカス グループを使用したタブ ループの定義 (12:12)

iPadOS と Mac Catalyst には 2 セットのキーボード動作があります。矢印キーはエリア内を移動します。タブを押すとエリア間を移動します。これらの領域はフォーカス グループです。

ビューまたはビューコントローラーにも同じように設定しますfocusGroupIdentifier、それらは同じフォーカスグループに属しています。

self.focusGroupIdentifier = "com.myapp.groups.sidebar"

キーポイント:

  • focusGroupIdentifierフォーカス グループを文字列として識別します。
  • 同じ識別子を持つ環境はグループ化されます。
  • 子環境に独自のグループがない場合は、親環境のグループを継承します。
  • カスタム コンテナ ビューを使用して視覚的な構造を表現できます。

各フォーカス グループには主要な項目があります。タブがグループに移動すると、フォーカス システムがメイン項目を選択します。開発者は優先順位によって選択に影響を与えることができます。

extension UIFocusGroupPriority {
    public static let ignored: UIFocusGroupPriority // 0
    public static let previouslyFocused: UIFocusGroupPriority // 1000
    public static let prioritized: UIFocusGroupPriority // 2000
    public static let currentlyFocused: UIFocusGroupPriority // NSIntegerMax
}

キーポイント:

  • .ignoredは優先度が最も低いです。
  • .previouslyFocusedこのグループ内でフォーカスを受け取った最後の項目を示します。
  • .prioritized選択されたセルなど、システムがより重要であると見なす項目を示します。
  • .currentlyFocusedこれは現在重点を置いている項目であり、最も高い優先度を持っています。
  • 開発者は、プロジェクトの優先度をシステムによって提供される優先度よりも下げることはできませんが、別のプロジェクトの優先度を上げることのみ可能です。

CTA セルは、以下よりも高い位置に設定できます。.previouslyFocused優先順位を設定して、フォーカス グループの主要項目となるようにします。

// Customizing an item’s focus group priority

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = ...
    if self.isCallToActionCell(at: indexPath) {
        // This cell is not as important as a selected cell but should
        // be chosen over the last focused cell in this group.
        cell.focusGroupPriority = .previouslyFocused + 10
    }
    return cell
}

キーポイント:

  • cellForItemAt指定された位置にあるセルを返します。
  • isCallToActionCell(at:)この場所が強調表示する必要がある項目であるかどうかを判断します。
  • cell.focusGroupPriorityフォーカス グループ内のセルの優先度を調整します。
  • .previouslyFocused + 10最後にフォーカスした項目よりも上位にします。
  • 選択したセルのシステム優先度が高い場合でも、メイン プロジェクトとして優先されます。

フォーカス グループ構造をデバッグするときは、次のように使用できます。UIFocusDebugger.checkFocusGroupTree(for:)

po UIFocusDebugger.checkFocusGroupTree(for: environment)

キーポイント:

  • environmentビュー、ビュー コントローラー、フォーカス システムなどのフォーカス環境です。
  • デバッガーは、この環境から開始してフォーカス グループ構造を出力します。
  • 視覚的な構造に応じてタブループが動かない原因を確認するのに適しています。

Xcode では、フォーカス ループ デバッグ オーバーレイをオンにすることもできます。実行パラメータに追加する-UIFocusLoopDebuggerEnabled YESその後、Option キーを押したままにするとタブのサイクル順序が表示され、Option + Control キーを押したままにするとフォーカス グループが表示されます。

応答チェーンとキーボード コマンドの競合の処理 (19:16)

iPadOS 15 のフォーカス システムは、Tab キーや矢印キーなどの一部のキーボード コマンドを使用します。アプリが以前にこれらのキーを使用してカスタム キーボード ナビゲーションを構築していた場合、iOS 15 SDK にコンパイルした後、システム中心のナビゲーションが引き継がれます。

特定のキーボード コマンドをシステムの動作よりも優先する必要がある場合は、次のように設定できます。wantsPriorityOverSystemBehavior

keyCommand.wantsPriorityOverSystemBehavior = true

キーポイント:

  • keyCommandアプリで定義されたキーボード コマンドです。
  • wantsPriorityOverSystemBehaviorこのコマンドがシステムの動作より優先されるように要求します。
  • キーボードのナビゲーションを妨げないことが確実な場合にのみ設定してください。true

キー イベントを手動で処理する場合は、印刷ライフ サイクルを完全に実装し、未処理のキーを一貫して呼び出す必要があります。super

override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
    if (/* check presses of interest */) {
        // handle the press
    }
    else {
        super.pressesBegan(presses, with: event)
    }
}

キーポイント:

  • pressesBeganキーが押されたときに呼び出されます。
  • pressesこのイベントのキーストロークのコレクションが含まれます。
  • event対応していますUIPressesEvent
  • 条件分岐は、アプリが本当に気にするキーストロークのみを処理します。
  • super.pressesBegan(presses, with: event)他のキーをシステムと応答チェーンに返します。

応答チェーンとフォーカス システムは相互に同期します。電話becomeFirstResponderフォーカスの更新を推進します。 session iPadOS 15 に更新するときは、このタイプの呼び出しを確認すること、特にフォーカス更新コールバックでランダムに呼び出さないことをお勧めします。

重要ポイント

  1. 作業内容: 写真、ドキュメント、またはアセット グリッドにフル キーボード ナビゲーションを追加します。 実行する価値がある理由:allowsFocusコレクション ビューのセルには矢印キーでアクセスでき、Return キーで現在のプロジェクトを直接開くことができます。 開始方法:UICollectionViewController.viewDidLoad()設定collectionView.allowsFocus = true、次に使用しますcanFocusItemAtプレースホルダー セルをスキップします。

  2. 作業内容: カスタム描画キャンバス要素にキーボード フォーカスを追加します。 実行する価値がある理由: フォーカス システムにより、UIKit 以外のコンテンツの通過が可能になりますUIFocusItemそしてUIFocusEnvironmentアクセス。 開始方法: キャンバス オブジェクトを表すモデルまたはラッパー ビューにフォーカス プロトコルを実装し、オーバーライドします。canBecomeFocused、存在するdidUpdateFocus選択した境界線を更新します。

  3. やるべきこと: サイドバーが「フォーカス移動時のコンテンツの切り替え」をサポートするようにします。 実行する価値がある理由:selectionFollowsFocusリスト主導のコンテキスト切り替えに適しており、ユーザーは矢印キーのみを使用してさまざまなカテゴリを参照できます。 開始方法: サイドバーのコレクション ビューを設定するselectionFollowsFocus、次に使用しますselectionFollowsFocusForItemAtアラートをポップアップ表示する項目を除外します。

  4. 対処方法: 複数列の生産性向上アプリのタブ オーダーを調整します。 実行する価値がある理由:focusGroupIdentifierこれにより、視覚的な分割を表現し、タブが単純な読み取り順序でサイドバーのコンテンツをスキップするのを防ぐことができます。 開始方法: 次を使用して、サイドバー コンテナーのフォーカス グループ識別子を設定します。UIFocusDebugger.checkFocusGroupTree(for:)生成されたグループ構造を確認します。

  5. 対処方法: システム フォーカス ナビゲーションと競合するショートカット キーをクリーンアップします。 価値がある理由: iPadOS 15 では、Tab キーと矢印キーが統合されたキーボード ナビゲーションを提供するようになり、古いカスタム コマンドが無効になる可能性があります。 開始方法: 確認してくださいUIKeyCommandTab キーと矢印キーを使用するコマンドの場合、移行できる場合はキーを変更します。本当に保持する必要がある場合は設定してください。wantsPriorityOverSystemBehaviorフォーカスの動きが壊れていないことを確認します。

関連セッション

コメント

GitHub Issues · utterances