ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
iPadキーボードナビゲーション
iPadおよびMacのCatalyst Appでのキーボード操作性を向上させましょう。ハードウェアキーボードを使って主要機能へのアクセスを高速化したり、ビューやビューコントローラを操作したりする方法を紹介します。キーボードで操作可能な要素をカスタマイズする方法や、タブループをカスタマイズする方法についても確認します。
リソース
- About focus interactions for Apple TV
- Adding Hardware Keyboard Support to Your App
- Adding Menus and Shortcuts to the Menu Bar and User Interface
- Adjusting Your Layout with Keyboard Layout Guide
- Implementing Advanced Text Input Features
- Navigating an App’s User Interface Using a Keyboard
- UIKit
関連ビデオ
WWDC21
- 優れたMac Catalyst Appの条件
- iOS Appにおけるフルキーボードアクセスのサポート
- iPad Appを次のレベルに
- M1搭載Macにおける優れたiPad/iPhone Appの条件
- UIKitの新機能
WWDC20
-
ダウンロード
明るい音楽
マイケル・オクスです UIKitチームの エンジニアです iPadキーボードの ナビゲーションへようこそ ハードウェアキーボードと iPadの組み合わせは人気です Mac CatalystとiPadOS 15が パワフルなAPIを導入して あなたのAppでキーボード ナビゲーションをサポートします iPadOS上であらゆるAppを キーボードで操作する事にー ユーザーはとても慣れています タブキーがApp内の エリア間の移動を担当 Arrowキーはエリア内を移動 アイテムの選択は iPadOS上ではReturnキー Mac Catalystでは スペースバー もしこれらのキーコマンドを カスタムして使用している場合 これらは使用できなくなります 解決方法は後で まず キーボードナビゲーションから "Photos"では タブキーを押して サイドバーのLibraryから フォーカスを移動 右側のグリッドへ移動します ここでArrowキーを使い グリッドの中で写真を ナビゲートできます 必要な写真を見つけたらー Returnキーで選択
iOS 15 SDKでコンパイルすれば この動作は自動的に有効化されます テキストフィールドやテキストビュー そしてサイドバーで有効です また他のコレクション テーブルやカスタムビューでも 優れた体験のために オプトインすべきです 使い方を今から紹介します これは 素晴らしい機能ですが Appの全てのエレメントをキーボードで 制御可能にするわけではありません キーボード操作は元々ー Appの主要な機能へのアクセスを 目的としています テキスト入力やリスト作成 コレクション閲覧など ボタンや セグメントコントロールはー 一旦おいておきます フルキーボードアクセスがー すでに こういった機能を提供しています フルキーボードアクセスについては 以下を参照してください "iOS Appにおけるフルキーボード アクセスのサポート" iPadOSのキーボード ナビゲーションはtvOSとー 同じフォーカスシステムを使います tvOS Appを 書いたことがあるならー 多くのAPIに 既視感を持つでしょう ただし 新しいAPIやー 動作の違いも気づくでしょう フォーカスシステムに ついては "Focus Interaction in tvOS"を みてください
このビデオでは Appのコンテンツを これまで以上にー Focusable = フォーカス可能に する方法と その見た目のカスタマイズについて お話します 知っておくべきサイドバーの 特別な動作をご覧いただき その次にAppの構造を定義する 新しい方法である フォーカスグループについても お話しします 最後に重要な 変更について話します Responder Chain についてです では今から UIのエレメンツをフォーカス可能に する方法を見てみます canBecomeFocusedは 「信頼できる唯一の情報源」で UIFocusItemの 読み出し専用プロパティです オーバーライドしてtrueを返し フォーカス可能にします Focusアイテムとは何でしょう? フォーカスシステムの根幹は 2つのプロトコル UIFocusItem と UIFocusEnvironment で FocusItems は文字通り フォーカスを当てることができる物 FocusEnvironmentsは フォーカス可能アイテムのー 階層を定義します UIViewは 両方のプロトコルに準拠しており どのビューもそれ自身が フォーカス可能で さらにフォーカス可能な サブビューも内包可能です 一方 UIViewControllerは それ自体はビジュアルと 連携しないので UIFocusEnvironment のみに準拠しています あなたのオブジェクトをこの2つの プロトコルに準拠させる事もできます そうすれば Metalのような 他の技術でレンダリングされた コンテンツも フォーカス可能にできます キーボードナビゲーションで 最も有力な候補は テーブルのセルや ビューのコレクションです UIKitが便利なAPIを提供します Subclassの必要は ありません テーブルまたはコレクションビューで allowsFocus を true にすると 全ての cell が フォーカス可能となrます サイドバーでの allowsFocus は true が初期値です
更に細かな制御には delegate で canFocusItemAtindexPath を使って セルごとにフォーカス可能かを 制御します これらの方法は canBecomeFocused をー 上書きしないセルにのみ 影響します
フォーカスアイテムが 想定通りの挙動をしない場合 デバッグ用のツールもあり lldb で UIFocusDebugger. CheckFocusability(for item:) を デバッグするアイテムを指定して call します 例えばフォーカス可能としたい ビュー を渡します フォーカスシステムが このアイテムをー フォーカスしないと 判断した理由が示されます
あなたのUIをフォーカス可能とする 方法について話しました フォーカスしたアイテムの 見た目について 詳しく見てみましょう システムを通じて よく使われるのはー 2つのスタイル まずHalo Effectについて macOSのフォーカスリングと 似ています 実際これはMac Catalystの デフォルトエフェクトです そしてiPadOSでも使用可能 focusEffect に UIFocusHaloEffect を設定します 引数なしで初期化した場合 システムはhaloの形状を 推定します コンテンツに合わせる為に 形状のカスタマイズもできます 画像の四角が丸い場合ー haloもそれに合わせて 角丸にすべきです
UIFocusHaloEffectには 複数のイニシャライザーがあり 角丸を含む 他の形状も描画できます このイニシャライザーで haloの形状をー ビューのコンテンツに合わせた アウトラインで作成します Halo Effectは ビュー階層の中の レイヤーの制御も可能です haloが画像のバッジの上に レンダリングされています でも画像とー バッジの間なら もっと見栄えが良いですね 画像のビューを referenceViewに指定すると UIKitはhaloを 画像の上で バッジの下に レンダリングします referenceViewは ビュー階層の中の haloの相対的順番を 定義します またcontainerViewを 指定して halo effectの superviewを定義する事もできます フォーカスされたアイテムの 直接のsuperviewが コンテンツをクリップしている時に 便利です
いずれもオプションですが 結果が想定通りでないー 場合のみこれを適用します
完全に不透過な画像のような コンテンツを表示時するー コレクションビューと テーブルビューのセルは haloを表示すべきです その他の全てのケースで セルにフォーカスが当たる時は 強調表示としましょう つまりバックグランドを あなたのAppのTint colorに するということです そしてテキストやアイコンの フォアグラウンドカラーとの コントラストを調整します このハイライト表示は UIFocusEffectでは入手不可で その代わり iOS 14で登場した backgroundと content configurations で この外観が自動的に得られます 詳しくは "Modern cell configuration" をご覧ください content configurationsや background不使用の場合 サンプルAppで 全てのケースで 必ず正しい色の取得方法を 提示します 必ず確認して下さい
custom appearanceを 適用の場合 まず focusEffect を nil に設定し システムスタイリングを 無効にします 次はoverride didUpdateFocus (in: context withAnimationCoordinator :) あなたのフォーカスアイテムに 適用します nextFfocusedItemが selfの場合 フォーカスを表示する スタイリングを previousFocusedItemが selfだった場合 フォーカスされていない 外観を表示します 次または前回の focused itemが この状況に合っている 場合のみ didUpdateFocus(in: context)で 変更する必要があります previousFocusedItemのー 過去の状況に起因します nextFfocusedItemも同様です 親子の呼出関係にある全ての ビューコントローラーで didUpdateFocus(in: context)が 呼ばれるためです 子のフォーカスの変更に 親も影響されるため 非常に柔軟な実装が 可能になります ここからサイドバーや類似した コンテキストが変化するUIを 詳しく見ていきましょう SelectionとFocusは それぞれ別のコンセプトです ただしサイドバーでは フォーカスを移動すると selectionが続きます
タップして 新しいセルを選択すれば フォーカスもそのセルへ移動します
これを "selection follows focus" と呼びます この動きをしてほしいセルが 大部分を占める テーブルやコレクションビューの プロパティーに設定します 特定のセルの 挙動を変更する場合 selectionFollowsFocus ForItemAtIndexPath をー delegateで実装して selectionFollowsFocus を無効にすると 破壊的動作を行うセルを 選択したときに便利です 同じカラムで新しくビューコントローラを プッシュしたり アラートを表示する時などです
例えばPhotosでは "new album"のセルが選択されると アルバム名を決める ダイアログが表示されます
delegateを使用する時 このproperty値は重要です selectionFollowsFocusを コレクションビューに設定して 大部分のセルの動作を決定し delegateで特定のセルに 独自の挙動を設定します システムは選択時に 両方の値を見て 正しい挙動を選択します
ではフォーカスグループを 見てみましょう Appの構造を表現する キーボードナビゲーションの 新機能です UIKitは階層構造から自動的に フォーカスグループを推測します しかし同時にタブキーで 移動するフォーカスのー カスタマイズも可能です directional focusは tvOSのみで可能です Siri Remoteのスワイプ またはキーボードのarrowキーで 全てのエレメントに到達可能です 一方iPadOSや Mac Catalystでは キーボードでのナビゲートに 2つの方法があり arrowキーとタブキーを使います tvOSと異なり arrowキーはApp内の 定義したエリア内での フォーカスの移動に使います これらをフォーカスグループと 呼びます 例えば リマインダ内のナビゲートに 上下のarrowキーを使用い
リストのナビゲートには タブキーを押して サーチフィールドに フォーカスし そして再度タブキーで リストに移動します もう一度タブキーを押すと フォーカスは リマインダーに戻ります
リマインダーやサーチフィールド そしてリスト これらはそれぞれが フォーカスグループで そしてタブキーで フォーカスが相互に移動します
フォーカスが グループ間を移動した時 グループ内のアイテムが選択され これをグループの プライマリアイテムと呼びます
グループのプライマリアイテムは 変化します 例えばここで2番目の リマインダーをフォーカスしました フォーカスシステムは グループを切り替えた時 フォーカスアイテムを記憶します そしてリマインダーに戻る時 2番目のアイテムに フォーカスし このアイテムがグループの プライマリアイテムとなります タブキーが各グループの プライマリアイテムに接続 そして相互に移動します これをtab loopと呼びます
一部の環境では 独自のフォーカスグループが 初期設定として定義されています コレクション同様にスクロールビュー テーブルビューを含む テキスト入力クラスや テキストビューも 明示的にグループが 定義されていない場合 親環境のグループを継承します 大体の場合superview またはビューコントローラです 例えば初期設定として 全てのセルは自動的に コレクションビューの グループに属します そのグループの中で セル間をarrowキーで 移動できます 独自にフォーカスグループを 定義する場合 focusGroupIdentifierを ビューやビューコントローラに設定し 2つの環境が 明示的あるいは継承によって 同じidentifierを共有する場合 同じグループの一部となります グループのプライマリアイテムの カスタマイズには そのアイテムに FocusGroupPriorityを設定して グループ内の重要度を指定します 最高の優先値を持つ 可視化したアイテムは グループのプライマリアイテムで システムは事前に設定された 優先順位を割り当てます デフォルトは ignored で previouslyFocused prioritized の順で アイテムの優先度が 上がっていき 現在フォーカスされているセルが currentlyFocused で 最も優先度の高いものです システムが提供する優先値より 低い値を 設定することはできません 代わりの他のアイテムの 優先値を上げます
例えばセルに .previouslyFocused より高い 優先値を設定して カスタムセルにすると このセルはこれまでの グループの フォーカスアイテム以上に 優先度が上がるので カスタムセルと 以前のフォーカスセルが 共に可視化しても カスタマイズしたセルは プライオリティが高い為 プライマリアイテムとなります アイテムのグループ化に ついて話しました 今からグループの分類に ついて話します 再びRemindersです 説明した通り それぞれの テーブルとコレクションビュー そして各text fieldが 自身のグループを定義します
前のようにタブキーを 繰り返し押せば フォーカスはサーチフィールド からサイドバーのリストへ そして右側のremindersへ移動します
これはUIKitの初期設定です 自身でこうしたコンテナビューを 作成する場合 フォーカスはサーチフィールドから remindersへ直接移動します 全groupsは 読み出し順に並び 文頭から文末 上から下へ続くからです フォーカスシステムは サイドバーが 独自のカラムだとは認識しません
サーチフィールドとリストが 同じブロックで連続である事を 示すために 共通の親グループを指定します サイドバーのコンテナビューに focusGroupIdentifierを 設定します この新グループは直接 focusable itemsを含みませんが しかしタブループは 検索フィールドグループから リマインダーへ移動する前に リストグループに移動します
多くのスタンダードUIKit プレゼンテーションはー こうした中間グループを 既に提供してきました カスタムコンテナビューの場合は 共通の親で独自の focusGroupIdentifierを宣言します フォーカスグループは Appの視覚的構造を定義する 簡単な方法です タブループの固定順序を 定義する必要はありません 代わりにシステムは フォーカスグループを利用し 読み取り方向、レイアウト、 および可視性を考慮した タブループの順序を導き出し 一貫したエクスペリエンスを 提供します App内でフォーカスグループを カスタマイズする際 UIFocusDebuggerが 味方になります checkFocusGroupTree (for environment:) を呼出した時に 渡された環境から始まる フォーカスグループの 構造を出力します また フォーカスシステム そのものを渡して 現在の全てのグループを 見る事もできます
テキスト化された構造も 役に立ちますが もう1つの デバッグツールがあります 先ほどお見せした フォーカスグループと Remindersのスクリーンショットを 覚えていますか? 実際のAppを操作して 確認できます focus loop debuggerを 有効にすると App上でoption keyを 押している間 タブループの順番を可視化します
optionとcontrolを クリックすれば フォーカスグループを可視化します このモードでは グループのプライマリアイテムは 点線で強調されます
有効化は Scheme settingsで Xcodeで"Run"を選択 続いて"Arguments" UIFocusLoopDebuggerEnabled を 起動引数に追加 これをYESにし チェックボックスで有効にします XcodeでAppを起動すると このデバッグ画面が あなたのAppに表示されます これがiPadOSとMac Catalystでの フォーカスの基本 キーボードナビゲーション についてもう1つ responder chainに ついてです responder chainと フォーカスシステムの両方が キーボード入力に係っており UIKitはこれらのシステムと 可能な限り同期して フォーカスされたアイテムが常に ファーストレスポンダー内にあるか ファーストレスポンダー自体とします
テキストフィールドと コレクションビューセルを備えた 簡略化されたビュー階層を 見てみましょう 現在テキストフィールドが フォーカスされており 実線のリングで示されています 破線のリングで示されている ファーストレスポンダー でもあります フォーカスがこのコレクション ビューセルに移動すると UIKitはファーストレスポンダーも このセルに移動しようとします canBecomeFirstResponderで このセルがfalseを返す場合 システムはレスポンダーチェーンを さかのぼって trueを返す レスポンダーを見つけます この場合そのレスポンダーは セルのビューコントローラーです
逆もまた真であることに 注意してください ファーストレスポンダーが 変更されると フォーカスシステムは そのレスポンダー内の 新しいフォーカス可能な アイテムを見つけようとします ファーストレスポンダーと フォーカスアイテムの関係により キーイベントは常に フォーカスアイテムに配信され そこからレスポンダーチェーンを 上に移動します これにより いくつかの興味深い 新しい動作が可能になります 例えば セルがキーコマンドに応答して フォーカスされると キーコマンドが そのセルに配信されます これを使用する方法については サンプルAppをご覧ください iPadOS 15向けに Appを更新するときは becomeFirstResponderを 呼び出す場所に注意してください レスポンダーチェーンと フォーカスは同期されているため ファーストレスポンダーを変更すると フォーカスが強制的に更新されます これはユーザーにとって非常に 混乱を招く可能性があります 通常 特にフォーカスの更新に応じて becomeFirstResponderを 呼び出さないようにするのが最善です
フォーカスシステムは すべてのAppで一貫した エクスペリエンスを提供します これを行うには特定のキーコマンドの 優先順位が必要です Appがタブや下向き矢印などの キーコマンドを使用している場合 iOS 15 SDKでコンパイルすると そのキーコマンドは機能しなくなります このキーコマンドを使用して 独自のカスタムキーボード ナビゲーションを作成する場合は そのままにしておくことができます 以前のバージョンで動作し iPadOS15では フォーカスシステムが引き継ぎます それ以外の場合は このキーコマンドを再マップします このキーコマンドを 本当に使い続けたい場合は キーボードナビゲーションが 壊れないことを確認してから wantsPriorityOverSystemBehaviorを trueに設定してください キーボードショートカットの 改善について詳しく知りたい場合は 「iPad Appを次のレベルに」 をご覧ください pressesBegan、pressesChanged、 pressesEnded、pressesCancelledを 実装してマニュアルでプレス イベントを処理する場合は これらすべてのメソッドを実装し 処理しないプレスイベントは 確実にsuperを呼び出してください iPadOS 15とMac Catalystの キーボードナビゲーションは ユーザーにとって強力なツールです コレクションビューとテーブルビューを フォーカス可能にして 優れたユーザーエクスペリエンスを 提供します キーコマンドを更新して キーボードナビゲーションと 衝突しないようにします サンプルAppも チェックしてください このAppには 優れた検索エクスペリエンスの構築 カスタム選択 フォーカスガイドなど さらにいくつかの機能が 示されています iPadOS 15でキーボード ナビゲーションを用いた あなたの作品を 楽しみにしています ありがとう 明るい音楽
-
-
3:01 - canBecomeFocused
override var canBecomeFocused: Bool { true }
-
4:00 - allowsFocus
class MyViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() self.collectionView.allowsFocus = true } }
-
4:23 - canFocusItemAtIndexPath
class MyCollectionViewDelegate: NSObject, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool { return true } }
-
4:40 - UIFocusDebugger checkFocusability(for:)
po UIFocusDebugger.checkFocusability(for:)
-
5:48 - UIFocusHaloEffect
let focusEffect = UIFocusHaloEffect(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius, curve: .continuous) self.focusEffect = focusEffect
-
6:03 - ReferenceView and 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
-
7:43 - Custom focus effects
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. } }
-
9:08 - Selection Follows Focus
var selectionFollowsFocus: Bool
-
9:16 - Selection Follows Focus for Item at Index Path
func collectionView(_ collectionView: UICollectionView, selectionFollowsFocusForItemAt indexPath: IndexPath) -> Bool { return self.action(for: indexPath).type != .showAlert }
-
12:12 - Focus Group Identifier
self.focusGroupIdentifier = "com.myapp.groups.sidebar"
-
12:52 - UIFocusGroupPriority
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 }
-
13:40 - Focus Group Priority on a cell
// 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 }
-
15:46 - UIFocusDebugger checkFocusGroupTree(for:)
po UIFocusDebugger.checkFocusGroupTree(for:)
-
19:16 - wantsPriorityOverSystemBehavior
keyCommand.wantsPriorityOverSystemBehavior = true
-
19:36 - pressesBegan
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) { if (/* check presses of interest */) { // handle the press } else { super.pressesBegan(presses, with: event) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。