ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
iPad Appを次のレベルに
より優れたiPad Appを構築しましょう。卓越したSceneをサポートして、集中したインタラクションを途切れさせない方法を紹介します。キーボードショートカットとキーボードショートカットインターフェイスを使用して、ユーザーが飽きずに素早く操作できるようにします。最新のポインタ拡張がAppの生産性向上にどのように役立つのかを検証します。
リソース
- Adding Hardware Keyboard Support to Your App
- Adding Menus and Shortcuts to the Menu Bar and User Interface
- Enhancing Your iPad App with Pointer Interactions
- Human Interface Guidelines: Pointing devices
- UIKit
関連ビデオ
WWDC21
WWDC20
WWDC19
-
ダウンロード
♪ (iPad Appを次のレベルに) こんにちは 私はクリス UIKitのエンジニアです 後ほど同僚のアナントと モハメッドが加わります iPadは そのパワー と性能により 多くの人にとって 重要なデバイスです このビデオではエキサイティングな 幾つかの新機能について学びます あなたのiPad Appのレベルアップに ご活用ください では マルチタスクやシーンの 最新の進化をお見せします 次に アナントが キーボードショートカットに関する パワフルな改良点について 説明します 最後にモハメッドが システムポインタに関する 最新の機能強化を 紹介します 早速マルチタスキングについて ご紹介しましょう iPadOS 13で AppのUIの 複数のインスタンスを実行する 機能が導入されました iPadOS 15では シーン表示用の新規APIと 状態復元の強化により このエクスペリエンスを 改善させています これらの拡張機能は 既存のUIScene インフラストラクチャに基づいて 構築されています まだUISceneを 採用していなければ 「Introducing Multiple Windows on iPad」 をご覧ください キーコンセプトについて ざっと説明しましょう 1つのシーンはAppのUIの 1つのインスタンスを表します シーンのコンポーネントの構造は シーン構成によって定義されます 少なくともシーンのロールと デリゲートクラスを定義します 名前 ストーリーボード シーンサブクラスも指定できます シーン構成は Info.plistで宣言するか UISceneConfiguration オブジェクトで 実行時に作成できます シーンのコンテンツは NSUserActivity で表されます これらのアクティビティは シーンの要求や 状態の復元で使用されます シーンは シーンデリゲートで管理されます このデリゲートは UI設定 ライフサイクルイベントへの対応 状態の保存 復元 の役割を担います 最後にシーンは シーンセッションでトラックされます シーン自体は バックグラウンドにある際に システムが切断 再接続します シーンセッションは 接続状態に関係なく シーンを追跡し 起動中も持続します セッションは システムのAppスイッチャと 考えることができます スイッチャの各アイテムは シーンセッションに対応します システムにシーンを リクエストする場合 このリクエストを カスタマイズするための オプションオブジェクトを 提供できます iPadOS 15には ウィンドウシーン専用の 新しいオプションサブクラスが あります このサブクラスを使用すると プレゼンテーションスタイルを 指定できます ウィンドウシーンの表示スタイルは ワークスペース内の 他のシーンに対して シーンがどう表示されるかを 決定します 考えられる値は 以下の3つです プロミネント 標準 そして自動です これはプロミネントプレゼンテーションの シーンです このワークスペースは モーダルに表示され 背後のシーンが 暗くなっています モーダルなので キャンセル 閉じる または 完了ボタンが表示されます この新しいスタイルは 新たなシーンの舞台と 考えられます 新しいマルチタスクコントロールで 他のシーンと同じように再配置でき 更にAppシェルフに移動して 後で使用することもできます このスタイルがシーンに 適しているかどうかを検討する際 覚えておくべきガイドラインが 幾つかあります まずプロミネントシーンは 単独で利用可能である必要があります 他のシーンのための オプションやツールを 提供するためには使用できません 完了 または閉じる ボタンを提供する必要があります また ドキュメントやファイルなど App内の特定コンテンツ専用に する必要もあります この専用コンテンツスコープは シーンのアクティブコンディションで 定義する必要があります アクティブコンディションの 詳細については 「Targeting Content with Multiple Windows」 を参照してください この例では Safariが標準スタイルで 新規シーンを開く方法を 示しています それらは並べて表示されており それぞれとのインタラクションが可能で いずれもメインシーンの 全機能を提供します スタイルとして自動を 指定してもよいです この値は要求に応じて 最適なスタイル を 選択する必要があることを システムに通知します iPadOS 15はスタイルで プレゼンテーションを カスタマイズする方法を 提供するだけでなく 新しいシーンでコンテンツを 開くことも簡単にできます Macではコンテキストメニューに 新規ウィンドウで開く 項目が 表示されることが多いです UIWindowScene.ActivationActionで iPad Appでもこの馴染みある 体験を得られるようにすべきです これはメニュー、ボタン、バーボタンアイテムで 使用できる シーンをリクエストする 新たなUIActionのサブクラスです この機能をAppに追加するには まず UIWindowScene.ActivationActionを 作成します アイテム選択時に実行される クロージャで初期化します クロージャは新規シーンのコンテンツを表す ユーザアクティビティを持つ ActivationConfigurationを返します 最後にそのアクションを メニューに持たせればOKです iPadと MacCatalystでは 「新規ウィンドウで開く」項目が メニューに表示され これを選択すると 新しいシーンが表示されます iPhoneでは 複数シーンが サポートされていないため アイテムは自動で 非表示になります その場所に別のアイテムを 表示したい場合 別のアクションを 指定することもできます これを実装するために コードを更新しましょう 代替アクションの 作成から始めます 複数のウィンドウを 使用できない時に このアクションのタイトルと 画像が表示されます 次に作成したアクションを alternateとして渡して シーンのActivationActionの イニシャライザを更新します 代替アクションを 提供すると iPadとMacCatalystの メニューには 「新規ウィンドウで開く」項目が 表示されますが iPhoneでは 「Show Details」項目が表示されます コード内の条件分岐なしで 全て実行されます これらメニュー項目の追加は 新規シーンでコンテンツを開く為の よく知られる分かりやすい方法です ただ これだけではありません iPadは タッチファーストデバイスです 一回のジェスチャで シーンをすぐに開けます Notes Appでは セルをピンチアウトすると 新しいシーンでノートが開きます シーンはセルから最終位置まで インタラクティブに アニメーション化されます Appでこの機能を提供するには 2つの方法があります コレクションビューを 使用している場合 新しいデリゲートメソッドが あります 他のビューでは UIWindowScene.ActivationInteractionを 使用します これらは共に プロミネントスタイルで シーンを提示するだけのものです コレクションビューでこの ジェスチャをサポートするには sceneActivationConfigurationForItemAt indexPath という名前の 新しいデリゲートメソッドを 実装します これは前のコンテキストメニューの例と 酷似しています 新しいシーンのコンテンツの ユーザアクティビティを作成し 次にそのアクティビティを含む ActivationConfigurationを返します 全てのセルが新らしいシーンのオープンを サポートするわけではないかもしれません ジェスチャが始まるのを防ぐには nilを返すだけです 他のビューでこのジェスチャを サポートするには UIWindowScene.ActivationInteraction を 作成します これは次の2つの引数をとる クロージャで作成されます interaction 自体と ビューの座標空間での interaction の point です これらを使用して ビューのさまざまな領域用の 固有のユーザアクティビティを 作成できます このクロージャは ユーザアクティビティと共に ActivationConfigurationを返します インタラクションには エラーハンドラも必要です 複数シーンに対応しない プラットフォームでは インタラクションが 無効になっていますが 構成の問題や システムリソースの不足が原因で エラーが発生することがあります ウィンドウシーンを表示する これらの方法はすべて 同じActivationConfiguration オブジェクトを使用していることに 気付いたのではないでしょうか 唯一の要件は ユーザアクティビティですが シーンリクエストオプションと ターゲットプレビューも 含まれています これらが提供されていない場合 システムは全力で これらを作成します ただし エクスペリエンス改善のために あなたがこれらを明示的に 提供することができます 例えば これらのコレクション ビューセルには サムネイル タイトル キャプションがあります セルをピンチアウトすると 多くの場合 サムネイルのコンテンツの拡大版を表示する シーンが表示されます 新しいシーンが セル全体から変形していることに 注目してください サムネイルのみから変形した方が 良いはずです この効果を実現するには ActivationConfigurationを 以前のように作成します 次に セルがサムネイル セルであるかどうかを確認します そうであれば セルのサムネイルビューで ターゲットプレビューを作成し これを構成に設定します ActivationConfigurationのカスタム プレビューを提供すれば 移行はもっと良くなります よく見てみましょう これで セル全体から 移行するのではなく サムネイルのみから移行し セルの残りの部分はそのままです ActivationConfigurationを使用すれば Appの任意のシーンを リクエストできます ただし 自身でシーンを作成せず ファイルを表示したい だけの場合もありますよね iPadOS 15では これが非常に簡単です QLPreviewScene. ActivationConfiguration という UIWindowScene. ActivationConfiguration の 特別なサブクラスがあります プレビューシーン構成を返すと システム管理のプレビューシーンが リクエストされます 煩わしいシーンデリゲートや コールバックはありませんが App は Info.plist で 複数ウィンドウのサポートを 宣言する必要があります 新しいシーンで コンテンツを開くための 洗練された便利な方法を 提供することは重要です ただし シーンの状態を保存して 復元することも 同様に重要で 後でそこに戻れることが 円滑なエクスペリエンスとなります シーンがバックグラウンドに移行すると システムは その状態を表すNSUserActivityを シーンのデリゲートに要求します Handoff をサポートするか その場で作成されたものなら アクティビティは ルートビューコントローラの アクティビティかもしれません ここでは ユーザアクティビティを作成し テキストフィールドの内容を アクティビティの ユーザ情報辞書に保存します 最高のエクスペリエンスを 得る上で シーンの状態は コンテンツだけにとどまりません スクロール位置 カーソル位置 ファーストレスポンダの ステータスなどの視覚的状態 相互作用の状態も 保存する必要があります これらをそれぞれ 個別に保存するのではなく UITextFieldと UITextViewに interactionStateプロパティを 追加しました このプロパティはあらゆる インタラクション状態を含む 単一のオブジェクトとして 提供します オブジェクトには コンテンツ自体は含まれておらず これはユーザアクティビティに 保存するための追加情報です テキストフィールドの インタラクション状態を 保存することにより コードを更新しました 今 コンテンツとインタラクション状態を 共に保存することにより ユーザアクティビティには 正確な復元に十分な 情報が含まれています iPadOS 14では 状態復元は 少し難しいかもしれません シーン接続時に状態を 復元しようとした場合 ストーリボードとビューが まだ読み込まれない事に気付くでしょう シーンが フォアグラウンドに移行する時に 復元を行う場合 これが初めてかどうかを トラックする必要がありました iPadOS 15では 状態復元用の 新たなデリゲートメソッドを 明示的に使用して この問題を解決します これはシーンが接続され ストーリーボードが読み込まれた後 フォアグラウンドへの 最初の移行前にのみ呼び出されます Appがストーリーボードを 使用しているかどうかにかかわらず この新しいコールバックで 状態を復元します 前例で保存されたアクティビティからの 状態復元は システムが sceneRestoreInteractionState を 呼び出すことから始まります 次に テキストフィールドの 内容を復元します 最後に インタラクション状態を 復元します インタラクション状態の復元前に コンテンツを復元することが 必須です 最後に 状態の同期的復元は 複雑になることがあります データベースアクセスや ファイルロードが必要な場合があり その間空のUIを表示したくありません これを考慮するために iPadOS 15ではAppで 短期の延長をリクエストできます この延長中 メインRunLoopの実行を 許可しながらも 起動イメージが 表示されたままになります コンテンツが読み込まれると Appは復元の完了を 通知する必要があります この延長は短時間であり ネットワークアクセス等の 長時間の実行が見込まれるタスクで 使用するものではありません Appが完了通知に失敗した場合 または時間がかかりすぎる場合 システムによって閉じられます 長時間の状態復元を使用するには シーンから延長を要求する ことから始めます 次に 非同期操作を開始します コンテンツが読み込まれると これを復元し シーンに復元を 完了するように指示します シーンには 新しく復元された UIが表示されます Appでマルチタスクをサポートすれば Appにが輝きが出ますが 真の意味で高いレベルの iPad Appになるには やるべきことがたくさんあります さて 今からアナントになります ありがとう クリス! 私はアナント UIKitのエンジニアです 人はiPadが手で持つのに 十分軽量でありながら すぐに物理的キーボードに 接続できることを好みます 人はAppが素晴らしい キーボードサポートを 備えていることを 期待しています iPadOS 15にはAppの キーボードショートカットを 更にレベルアップする新機能が 幾つか導入されています iPadOS 15はキーボード ショートカットを検出する まったく新しいインターフェイスを 備えています 各コマンドを 使い慣れたカテゴリへと構造化し App の iPad バージョンと Mac Catalyst バージョンとの 差を無くします メニューには システムのどこからでも呼び出せる 便利な検索機能があります ショートカットを タップで起動することもできます UIKeyコマンドでキーボードショートカットを サポートする方法や コマンドがレスポンダチェーンに ディスパッチされる方法を 復習する必要がある場合は 「Support hardware keyboards in your app.」を ご覧ください Mac Catalystでは Appごとに メインメニューと呼ばれる グローバルメニューがあり これは画面上部の メニューバーに表示されます メインメニューはファイルや編集等の 幾つかのカテゴリサブメニューで 構成されており これらのカテゴリメニューには Appのコマンドを全て含む 追加のサブメニューが含まれています iPadOS 15では メインメニューシステムを iPad Appに導入しました この新しいショートカット インターフェイスは コマンドキーを押し続けると 表示され このメニューを表示します 現状 iPadのメインメニュー システムは Macとは若干異なって 表示されています Macは各カテゴリ内の 完全なサブメニュー階層を 表示しますが iPadはそれらの階層を フラット化します Macでは 無効で実行できないコマンドが グレー表示されますが iPadでは非表示になっています さらに iPadのショート カットメニューは Appの キーボードショートカットを見つけ 易くするように設計されています Macのメニューバーのように キーレスコマンドは表示されません デフォルトでは メインメニューには ファイルや 編集等のシステムカテゴリメニューが 全て含まれています これには 取り消す やり直す ペースト スタイルを合わせる などの 全てのシステムコマンドが 含まれます PadOS 15はこのシステム コマンドのリストに プリントコマンドを追加します Appは UIApplicationSupportsPrint キーを Info.plistに追加することで 実装できます iPadOS 15のiPad Appが メインメニューシステムを サポートするようになり Mac Catalyst Appの様に UIMenuBuilder APIで カスタマイズできます 実際 Appの Mac Catalystバージョンを 既にお持ちであれば 作業は殆ど完了しています Appはビルダーでサポート済みの キーコマンドを 全てメインメニューに追加します これはAppがこれまでに キーボードショートカットを 宣言する方法からの変更点です つまり UIResponderのkeyCommands プロパティを オーバーライドするか Viewコントローラで addKeyCommand(_ :) を呼び出します このように定義されたコマンドは 引き続き機能しますが これらは新しいインターフェースの 別の未分類セクションに 表示されます Appのこのような キーコマンドの定義は削除して 代わりに メインメニューに追加しましょう メインメニューを カスタマイズするには AppDelegate の buildMenu(withbuilder:) を オーバライドします UIKitはApp起動時に このメソッドを呼び出し UIMenuBuilderオブジェクトを 渡します builder の system が main であることを 確認する必要があります その場合この builder で カスタマイズできます Appがタブを操作するために ファイルメニューのキーコマンドを 幾つか必要としているとしましょう App は UIMenu API で サブメニューを作成し そのメニューの子として 必要なキーコマンドを 追加するだけです 次に Appは builder オブジェクトの insertChild メソッドを呼び出し サブメニューを ファイルメニューに挿入します メインメニューシステム の既存の要素 この場合 ファイルメニュー等を 参照するために Appは要素の識別子を 指定する必要があります 組み込みのシステム メニューの識別子は UIMenu.Identifier で 定数として定義されています Appは独自のメニュー カテゴリも簡単に作成できます ここで Appは UIMenu APIを使用して ブックマークメニューを 作成します 次に Appは builderを使用して そのメニューを ルートメニューに挿入します この場合 システム表示メニューの後です たったこれだけです! これで ファイルメニューと同じように builderを使用して ブックマークメニューへと 更に挿入することができます UIKitが自動生成する 新しいメニューの 識別子を渡すだけです 今 識別子について 説明を続けています UIMenuBuilderでは メインメニューシステムの各要素に 個々のコマンドを含めて 強制的に一意の識別子を 持たせます Appがキーコマンドを挿入して コンテンツを リストやグリッドとして 表示するとしましょう 両コマンドは同じアクション つまり changeViewMode(_ :)を 共有します メインメニューシステムでは コマンドはそのアクションで 暗黙的に識別されます ですから これらのコマンドは 共に同じ識別子を共有します UIMenuBuilderは 異なる識別子を持っていないと 両コマンドを挿入することを 許可しません これらコマンドを区別する方法の 1つとして 異なる propertyList 値を それらに与える方法があります しかし もっと良い方法は 具体的に何をするかを説明する 固有のアクションを 各コマンドに与えるだけです builderはメインメニューの キーボードショートカットの組み合わせを 強制的に一意にさせます App が Command - I に GetInfoキーコマンドを 設定するとしましょう テキストスタイルメニューの システムのイタリックのショートカットは 同じショートカットを共有するので この挿入も失敗します 繰り返しますが 2つの解決策があります App は Get Info ショートカットを Control - command - I などの 既存のショートカットと 競合しないものに変更しましょう また Appはテキストスタイル コマンドが不要な場合は それを削除するようbuilderに 指示することもできます 挿入に重複が含まれている場合 UIMenuBuilderは その挿入に失敗し 重複したキーコマンドまたは 共有識別子のいずれかを示すエラーを コンソールログに記録します 挿入したメニューが 表示されていない場合 どこかに重複がある 可能性があります その場合 コンソールで このようなログを探します buildMenu(with builder:) の呼び出しが終了すると Appのメインメニューが Macメニューバーと iPadショートカット オーバーレイに表示されます ただ 問題が1つあります Appはブックマークを 名前または日付で並べ替える コマンドを含む サブメニューを追加しました ただし iPadのショートカット オーバーレイには サブメニュー階層が 表示されないので これらのショートカットがiPadOSでは 何を意味するのか分かりません このような状況では キーコマンドに より記述的な discoverabilityTitleを設定します 両方が提供されている場合 iPadOSは 通常タイトルよりも discoverabilityTitleを優先します さて 先ほど 個々のレスポンダで キーボードショートカットを 宣言するのではなく メインメニューシステムで宣言する 必要があることを説明しましたね ただし それでも レスポンダは メインメニューコマンドの アクションメソッドを 実装する必要があります キーコマンドがトリガーされると UIKitは自動でアクションを レスポンダにディスパッチします UIKitはAppのレスポンダチェーンの トラバースによりこれを行います アクションを実行できる レスポンダが見つかるとすぐに そのレスポンダのアクション メソッドを呼び出します チェーン内にアクションを実行できる レスポンダが存在しない場合 キーコマンドは実行されません UIResponderの 概念について初めての方であれば 「Support hardware keyboards in your app」に その仕組みに関する 詳しい紹介があります 「優れたMac Catalyst Appの条件」でも もう少し踏み込んで説明しています UIKitがチェーンに沿って レスポンダ検索を実行すると、 便利なUIResponder メソッドを2つ呼び出します Appはレスポンダで これらのメソッドを オーバーライドして キーコマンドを改善できます 1つ目は canPerformAction(_:withSender:) で これはUIKitが チェックに使用するものです レスポンダが アクションを実行できる場合 デフォルトでは これはtrueを返します 実行不可の場合 レスポンダにアクションメソッドを実装して false を返します レスポンダはオーバーライドして カスタムロジックを追加できます 例えば 開いているタブがない場合 WebブラウザはcloseTab コマンドを実行できないことを UIKitに通知できます UIKitがそのアクションの ターゲットレスポンダを 見つけることができないため コマンドは実行できなくなり ショートカットインターフェイス には表示されません このメソッドのオーバライドは 未処理の場合 superを呼び出す必要が あることに注意してください 他の便利なメソッドは validate(_ command:) です UIKitがキーコマンドの ターゲットレスポンダを見つけると そのレスポンダで このメソッドを呼び出し コマンドのコピーを渡します レスポンダは validate(_ command:) を オーバーライドして Appの現状態に合わせて コマンドの外観を更新できます ここで Appは現ページが ブックマークされているかどうかを基に toggleBookmark(_:) コマンドの タイトルを更新します このメソッドで設定された タイトルは 表示された際 ショートカット インターフェイスに反映されます iPadOS 15では UIKitはレスポンダチェーンを 大幅に変更しました Appがフォーカスシステムで キーボードナビゲーションを 採用する場合 レスポンダのトラバーサルは 最初のレスポンダではなく フォーカスされた アイテムから始まります この変更は キーコマンドで 非常にうまく機能します 例えば 写真では ユーザはキーボードだけで フォトライブラリを操作できます ユーザーがグリッド内の セルにフォーカスすると スペースバーを押して その写真が表示されます Command-Cを押し その写真をコピーして 別のAppに 貼り付けることもできます 各セルは様々なキーコマンド アクションを実装し レスポンダトラバーサルは フォーカスされたアイテムから始まるので キーコマンドはセルを対象とします つまり フォーカスシステムにより キーコマンドと レスポンダチェーンが レベルアップします これを利用して Appにおける 強力なコンテキスト ショートカットをサポートします 詳細については 「iPadキーボードナビゲーション」を ご覧ください 最後に iPadOS 15と macOS 12では キーボードショートカットの ローカライズを導入しています これらのSDKで Appをビルドすると システムはショートカット修飾子と 各キーボードレイアウトの入力を 自動でローカライズします 例えば コマンド バックスラッシュの ショートカットを見てみましょう ショートカットは 米国キーボードで機能しますが 日本語のキーボードでは バックスラッシュキーがないため 動作しません そこでシステムは日本語キーボードの ショートカットを再マップします つまり Appは ショートカット修飾子や入力を 自身でローカライズするのではなく システムに任せる 必要があります App全体か ショートカットごとに 自動ローカリゼーションを 選択することもできます システムがショートカットを ローカライズする際 これらを右から左へのレイアウト用に ミラーリングすることもします 例えば コマンド 左角括弧で 後ろに移動するショートカットを コマンド 右角括弧へと 切り替えます ショートカットの ミラーリングをオフにする場合 自動ローカリゼーションを完全に無効にせずに ミラーリングのみを無効にするには allowsAutomaticMirroring プロパティを falseにします iPadOS 15はキーボードショートカットに関する 大きなリリースです 今日取り上げたトピック以外にも 機能強化が たくさんあります これで 新しいマルチタスク機能を すべて取り込み 優れたキーボードサポートを 構築しました これから モハメッドが iPad Appを 最高レベルまで引き上げるための 幾つかの指針を紹介します どうも アナント! こんにちは モハメッドです iPadOS15が システムポインタに もたらす改善について お話しましょう iPadOS 13.4では iPadのタッチベースのUIとマウスや トラックパッドの精度を橋渡しする アダプティブシステムポインタを 導入しました ポインタのインタラクションに 慣れていない場合 慣れるために少し時間をかけて これらに関する以前のビデオを チェックしてください 「Build for the iPadOS pointer」では ポインタ相互作用APIについて 詳説しています 「Design for the iPadOS Pointer」では その背後にあるデザイン哲学を 掘り下げます そして App採用時のベスト プラクティスについて説明します iPadOS 15はiPadの デザイン言語と一貫した方法で お馴染みのMacユーザ インタラクションをもたらします 使いやすさと分かりやすさを 向上させる 新しい概念も幾つか紹介します その新しい概念の第1は バンド選択です これはMacを使用している人なら 誰でも知っているはずの 新しいポインタ固有の 複数選択のエクスペリエンスです iPadOS 15では コレクションビューをクリックして ドラッグすると ポインタが長方形に伸び コレクションビューは 長方形が包含する アイテムを選択します もちろん これはMac Catalyst Appで ご存知の Mac UI へと 自然に変換されます このインタラクションはリスト以外の UICollectionViews に組み込まれています iPadOS15では shouldBeginMultiple APIを介して 既存の1本指や2本指による 複数選択ジェスチャをサポートする UICollectionViewは この動作を自動的に取得します UICollectionView以外の場合も 新しい UIBandSelectionInteraction を使って このエクスペリエンスを Appへと簡単に導入できます この選択ロジックは 完全にあなた次第なので カスタムの選択動作を サポートできます そして 選択の変化に応じて UIを自由に反応させることが できます 開始するには ポインタが移動して インタラクション状態が変化した際に 呼び出される選択ハンドラで インタラクションを インスタンス化します 作成したら 他の UIInteractionと同様 インタラクションを ビューに追加します ハンドラでは インタラクションの状態と selectionRectの 変更に応答することで カスタム選択ロジックを 実装できます ここに ポインターの移動中 インタラクションの selectionRect内の アイテムに選択を設定する 簡単なやり方があります 次に マウスのメインボタンを離して インタラクションが終了すると 選択セッションが終了します 基本的な選択の他 UICollectionViewの 組み込みバンド選択は 幾つかの一般的な キーボードショートカットを そのままサポートしています 例えばドラッグ開始時にShift キーを押したままにすると 現在選択中のアイテムが 置き換えられるのではなく 既存の選択にアイテムが 追加されます コマンドを押したままにすると 選択範囲内のアイテムの 選択状態が切り替わります これはドラッグ開始時に 保持されるキーを提供する インタラクションの initialModifierFlags プロパティを使用して カスタムUIに実装できます 押されたすべての修飾子の セットなので 任意のキーの組み合わせに応答して App固有のカスタムまたは より高度な動作をサポートできます システムポインタへの 第2の追加機能は アクセサリに接続する機能です アクセサリは追加情報を伝達し 二次形状と一次ポインタとを 組み合わせることにより コンテキストのヒントが得られます たとえば 左の例では 2つの矢印が このビューを水平方向に ドラッグできることを示しています 右の例では プラス記号が この「カートに追加」ボタンに いくつかの追加のコンテキストを 提供します アクセサリとカスタムポインタ形状の 使用には 重要な違いがいくつかあります アクセサリはメイン ポインタに対して 視覚的に分離され 2次的です それらが異なる外観で レンダリングされ メインポインタとは別に アニメーション化される 場合があるという事実により このことが浮かび上がってきます それらは独立したユニットであり 様々なアイデアを伝えるために 組み合わせたり ポインタの周りに配置したり することができます それらは独立しているので 任意のポインタスタイルと 組み合わせることができます これは 同じアクセサリのセット つまり ドラッグ性を示す2つの矢印を さまざまなポインタスタイルと 組み合わせる方法のデモです 左側では それらは リフト効果と組み合わされており ポインタがビューと結合して 上げられます 真ん中では ハイライト効果と並び ポインタが角の丸い長方形に変わり ビューの下に入ります 右側は新しい UIPointerStyle.system() APIで デフォルトのシステム ポインタとともに表示されます そのため ポインタの有効性を損なわずに しかもAppのUIとの 深い関係を維持しながら それぞれの状況で理想的な 効果を使用することで この追加のコンテキストを 提供することができます ポインタスタイル間で アニメーション化するのと同じく システムは アクセサリの外観と消失を 自動でアニメーション化します 更に アクセサリの形状と 位置の間を シームレスに アニメーション化します 特定の効果がアクティブな際に アクセサリを移行する操作は 意味のあるものに することができます このような遷移を使用して 基盤となるUIの状態や動作の変化を 伝達することができます 左の例では プラス記号から禁止記号への移行は 以前は可能であった操作が 許可されなくなったことを 示している可能性があります ポインタアクセサリは UIPointerShapeと UIPointerAccessory.Position で構成され ポインタの中点からのずれと 上からの角度として 目的の位置を記述します 便宜上 UIKitは ポインタの周りの位置に 何かしらの既定値を提供します 既定位置が ニーズに全く適合しない場合 それらを出発点として使い 個々のプロパティを カスタマイズできます この例では 右上の位置からスタートし ずれをカスタマイズします また カスタム位置をすべて 定義することもできます この例では カスタムオフセットと角度で 位置を作成しています このセグメントの最初で見た例を 設定するために ビューで UITargetedPreview を作成し これを使用して リフト効果のある UIPointerStyle を作成します 次にスタイルの 新しい アクセサリ プロパティを 2つの矢印アクセサリを含む 配列に設定します UIKitは既製の矢印アクセサリ を提供するので エフェクトの左側と右側に 配置されたものを 2つ作成するだけです ここで ポインタが このビューの上にあると ビューが上がるにつれ 2つの矢印がアニメーション化され ドラッグできることを示します ポインタ効果のある ビューをドラッグできる この類のインタラクションを 実装しようとしたことがあれば このようなことに 気づいたかもしれませんね ポインタがポインタ領域端に 達すると 上がったビューから外れ 効果が終了します ポインタが動き回る際にポインタが ビューにくっつくのを防ぐので これは通常は望ましいことです ただし こうしたシナリオで 理想的とされるエクスペリエンスは ポインタ効果を安定させて ビューにラッチし ドラッグしながら それを辿ることです より上手い具合にこういった インタラクションを可能にするため iPadOS 15では UIPointerRegionに ラッチング軸の概念が 導入されています ある領域が所与の軸に沿って ラッチすると そのポインタ効果は メインマウスボタンが押された際 軸に沿ってポインタを辿ります 水平ラッチ領域を使用すると y軸に沿って対象との 接続関係を保持したまま x軸に 沿って自由にドラッグできます 垂直にラッチするものを使用すると y軸に沿って 自由にドラッグできます 両方に沿ってラッチするものを 使用すると 両軸に沿って自由に ドラッグすることもできます これらの新しいツールは 非常に 強力な新しいエクスペリエンスを 幾つか構築するために使用できます ここでは Pagesや Keynoteなどの ドキュメント編集Appに 組み込まれています この画像はバンド選択で 選択できます 画像を選択すると ドラッグインジケータが表示され ポインタがインジケータ上にあると ドラッグした場合に画像のサイズが どう変更されるかを示す アクセサリが表示されます 最後に ラッチングにより ポインタ効果とアクセサリが 軸ロックサイズ変更ジェスチャ に従うことができます これらは iPadOS15が iPadにもたらす 拡張機能のほんの一部です 当てはまる場合 それらを利用して Appの有用性を 最大限に高めてください プロミネントシーンを採用して Appのコンテンツに焦点を合わせ 途切れなく表示できるようにします 新しいキーボード ショートカットメニューで 複雑なタスクをすばやく実行し 新しいポインタ機能で生産性を 向上させることができます ご視聴ありがとうございました! ♪
-
-
4:56 - Build an "Open in New Window" action
let <#newSceneAction#> = UIWindowScene.ActivationAction({ _ in // Create the user activity that represents the new scene content. let userActivity = NSUserActivity(activityType: <#User Activity Type#>) // Return the activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: userActivity) })
-
5:43 - Use an alternate action with UIWindowScene.ActivationAction
// Create an action to use when multiple scenes are not available. let alternateAction = UIAction(title: <#Alternate Action Title#>, image: <#Alternate Action Image#>, handler: { _ in <#Perform Alternate Action#> }) // Create the scene activation action with the alternate. let newSceneAction = UIWindowScene.ActivationAction(alternate: alternateAction) { _ in // Create the user activity that represents the new scene content. let userActivity = NSUserActivity(activityType: <#Scene Activity Type#>) // Return the activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: userActivity) }
-
6:58 - Present a scene from a collection view with a gesture
func collectionView(_ collectionView: UICollectionView, sceneActivationConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIWindowScene.ActivationConfiguration? { // Get the item's user activity. guard let itemActivity = <#User Activity#> else { // Return nil if item can’t be opened in a dedicated scene. return nil } // Return the activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: itemActivity) }
-
7:28 - Present a scene from a custom view with a gesture
// Create an activation interaction. let newSceneInteraction = UIWindowScene.ActivationInteraction { interaction, point in // Get the activity for specific point in view. guard let userActivity = <#User Activity#> else { return nil } // Return an activation configuration. return UIWindowScene.ActivationConfiguration(userActivity: userActivity) } errorHandler: { error in // Present the content in another manner. <#Present Content#> } // Add interaction to the view. <#View#>.addInteraction(newSceneInteraction)
-
8:53 - Customize scene transition preview
// Create the activation configuration. let itemActivity = NSUserActivity(activityType: <#User Activity Type#>) let configuration = UIWindowScene.ActivationConfiguration(userActivity: itemActivity) // If the cell has a subview to use as the preview, create the custom preview. if let cell = collectionView.cellForItem(at: indexPath) as? <#Expected Cell Class#> { configuration.preview = UITargetedPreview(view: cell.<#Subview For Preview#>) } // Return the activation configuration. return configuration
-
10:18 - Save scene state
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { guard let viewController = self.window?.rootViewController as? <#Expected View Controller Class#> else { return nil } let stateActivity = NSUserActivity(activityType: <#State Restoration Activity Type#>) stateActivity.addUserInfoEntries(from: [ // Save content of a text field. <#Content Key#>: viewController.<#Text Field#>.text ]) return stateActivity }
-
11:16 - Save scene state with interaction state
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { guard let viewController = self.window?.rootViewController as? <#Expected View Controller Class#> else { return nil } let stateActivity = NSUserActivity(activityType: <#State Restoration Activity Type#>) stateActivity.addUserInfoEntries(from: [ // Save content of a text field. <#Content Key#>: viewController.<#Text Field#>.text, // Save interaction state of a text field. <#Interaction State Key#>: viewController.<#Text Field#>.interactionState ]) return stateActivity }
-
12:13 - Restore scene state
func scene(_ scene: UIScene, restoreInteractionState stateRestorationActivity: NSUserActivity) { guard let viewController = window?.rootViewController as? <#Expected View Controller Class#>, let userInfo = stateRestorationActivity.userInfo else { return } if let content = userInfo[<#Content Key#>] as? String { // Restore the content first. viewController.<#Text Field#>.text = content // Then, restore the text field’s interaction state. if let interactionState = userInfo[<#Interaction State Key#>] { viewController.<#Text Field#>.interactionState = interactionState } } }
-
13:15 - Restore scene state asynchronously
func scene(_ scene: UIScene, restoreInteractionState stateRestorationActivity: NSUserActivity) { guard let viewController = window?.rootViewController as? <#Expected View Controller Class#> else { return } // Request an extension. scene.extendStateRestoration() // Fetch content asynchronously. <#self.someAsyncFunction#> { result in <#Restore Content#> // Signal that state has been restored. scene.completeStateRestoration() } }
-
17:15 - Modify the main menu
override func buildMenu(with builder: UIMenuBuilder) { super.buildMenu(with: builder) // Ensure the builder is modifying the main menu. guard builder.system == .main else { return } // Use the builder to modify the main menu... }
-
17:37 - Add key commands to the main menu
// Create a menu with key commands. let tabMenu = UIMenu(options: .displayInline, children: [ UIKeyCommand(title: NSLocalizedString("New Tab", ...), action: #selector(BrowserViewController.newTab(_:)), input: "t", modifierFlags: .command), UIKeyCommand(...) ]) // Insert tabMenu into the File menu. builder.insertChild(tabMenu, atStartOfMenu: .file)
-
18:19 - Add a custom menu category
// Create a "Bookmarks" menu. let bookmarksMenu = UIMenu(title: NSLocalizedString("Bookmarks", ...), children: [...]) // Insert the Bookmarks menu into the root menu, after View. builder.insertSibling(bookmarksMenu, afterMenu: .view) // Insert another menu into the Bookmarks menu. let sortBookmarksMenu = UIMenu(...) builder.insertChild(sortBookmarksMenu, atEndOfMenu: bookmarksMenu.identifier)
-
22:38 - Customizing key command performability
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if action == #selector(closeTab(_:)) { return !openTabs.isEmpty } else { return super.canPerformAction(action, withSender: sender) } }
-
23:26 - Customizing key command appearance
override func validate(_ command: UICommand) { if command.action == #selector(toggleBookmark(_:)) { if currentTab.isInBookmarks { command.title = NSLocalizedString("Add to Bookmarks", ...) } else { command.title = NSLocalizedString("Remove from Bookmarks", ...) } } else { return super.validate(command) } }
-
28:47 - Supporting multi-selection using UIBandSelectionInteraction
// Support multi-selection using UIBandSelectionInteraction. let selectionInteraction = UIBandSelectionInteraction { [weak self] interaction in guard let strongSelf = self else { return } // Handle selection by responding to interaction state. if interaction.state == .selecting { strongSelf.selectItemsInRect(interaction.selectionRect) } else if interaction.state == .ended { strongSelf.finalizeSelection() } } view.addInteraction(selectionInteraction)
-
33:01 - Customizing a predefined pointer accessory position
var position = UIPointerAccessory.Position.topRight position.offset = 40.0
-
33:14 - Creating a custom pointer accessory position
let position = UIPointerAccessory.Position(offset: 23.0, angle: .pi * 1.25)
-
33:27 - Pointer Accessories
// Attach two arrow accessories to a lift pointer effect. func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? { let preview = UITargetedPreview(view: self) let style = UIPointerStyle(effect: .lift(preview)) if #available(iOS 15.0, *) { style.accessories = [ .arrow(.left), .arrow(.right) ] } return style }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。