ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
優れたMac Catalyst Appの条件
最高のMac Catalyst Appのためのベストプラクティス、ツール、およびテクニックを紹介します。iPad AppをmacOSに移植する際の主な考慮事項について説明し、インターフェイスとエクスペリエンスを改善するための詳細なコード例を示し、Mac Appを全てのユーザに配布する方法を紹介します。 このセッションを最大限活かしていただくためには、Mac Catalystの基礎を理解していることが推奨されます。WWDC21の「Mac Catalystの新機能」をご確認いただくと、iPad AppをMacに移植するための最新機能の概要がわかります。macOSへの習熟度を高めるための詳細情報については、WWDC20の「Mac Catalyst Appのインターフェイスの最適化」をご確認ください。
リソース
- Accessibility design for Mac Catalyst
- Adding Menus and Shortcuts to the Menu Bar and User Interface
- Bring an iPad App to the Mac with Mac Catalyst
- Building and improving your app with Mac Catalyst
- Human Interface Guidelines: Mac Catalyst
- Mac Catalyst
関連ビデオ
WWDC21
- iPad Appを次のレベルに
- iPadキーボードナビゲーション
- M1搭載Macにおける優れたiPad/iPhone Appの条件
- Mac Catalystの新機能
- UIKitの新機能
- UIKitボタンシステムについて
WWDC20
-
ダウンロード
♪ (優れたMac Catalyst Appの条件) こんにちは 「優れたMac Catalyst Appの条件」にようこそ Cocoaエンジニアの オーウェンモンスマです またUIKit担当の Dave Rahardja も 後で登場します 今日は素晴らしい Catalyst Appを作るための 3つの大事な点を お話します まず Mac Catalyst App 移行時に発生する 高レベルな 変化を説明します 次にコードの書き換えについて 掘り下げ MacでもApp体験改善が できるようにします 最後はApp配布情報に ついてです ではMac Catalystへの App移行から始めましょう 優れたCatalyst Appへの 第一歩は 優れたiPad Appがあり それが追加変更なしの状態で M1 Macで既に 動作していることです M1 Macがあれば Xcodeで Run Destination に "Designed for iPad" を指定して すぐに試せます こうした機能をiPadに 導入することで Mac Appのスタートは 幸先良いものになるでしょう AppがiPadでマルチタスクに 対応していると Macで複数ウィンドウのサポートを 自動的に得ることができます またUIMenuBuilderを使っていれば メニューはAppのメニューバーと ビューのセカンダリクリックで出る コンテキストメニューに 自動的に登録されます コピー&ペーストや ドラッグ&ドロップなどの システムワイドの動作を 自動的に橋渡しします AppがM1 Macでどのように 動作するかについて詳しくは 「M1搭載Macにおける 優れたiPad/iPhone Appの条件」の ビデオをチェックしてみてください しかし、あなたは物事をさらに 進めたいのでここにいます Macのチェックボックスを オンにすると すべてのMacに配布できるようになり 追加のAPIを使って Appをさらに洗練させることができます Trip Plannerという Appでやってみましょう Xcodeプロジェクト設定では Deployent Infoで Macのチェックをオンにします 右側に追加の ポップアップが出てきて "Scale Interface to Match iPad" と "Optimize Interface for Mac" から 選べます これをもう少し 見てみましょう とりあえず Xcode のツールバーの build をクリックして実行します するとAppがビルドされ 実行されます Appのビルドが失敗したら いくつか調べる点があります フレームワークやクラスには Mac Catalyst で使えないものもあるので ここでモダナイズしましょう これでMacで 実行できるようになるだけでなく iOS Appも改善されます OpenGLESを Apple独自のMetalフレームワークに移行すると GPUの全機能が利用できるようになります 廃止予定のAddressBookに代わり Contactsフレームワークが 将来を考慮した スレッドセーフな方法として 連絡先を扱います またUIWebViewも廃止予定で WKWebViewに置き換えられました またサードパーティーの 依存性もチェックしましょう こうしたフレームワークが XCFrameworkバンドルとして 配布されている場合 対応するMacバイナリが 提供されていることを確認してください Mac Appの作業を開始する時は プロジェクトビルド時には コンパイラ警告に注意し コンソールログで ランタイムメッセージを監視します こうした警告は Mac Catalyst プロセスとして 正常に動作させるための コードの修正方法を示しています また今後リリースされる macOSでも 動作するように サポートされているAPIだけを使いましょう Macで実行した時にAppが受け取る ライフサイクルイベントを 意識することも大切です 現在Appが AppDelegateで呼び出される ライフイベントに依存している場合 代わりに scene ライフサイクルを モニタリングして Appがデスクトップの 各ウィンドウのコンテンツ固有のイベントに 応答できるようにする必要があります Mac Catalyst Appは sceneDidEnterBackgroundイベントを iPad Appほど頻繁には 受け取らないので注意してください デスクトップウィンドウが 最小化または 閉じられる時 sceneは バックグラウンド状態に入ります Appが sceneDidEnterBackground で ドキュメント自動保存などの ルーティンワークをする場合 代わりにタイマーを使うと アクションが確実に 定期的に行われるようになります 最後に Catalyst App に scene が 存在しない場合でも フォアグラウンドで動作し続けます この状態はAppのウィンドウは 全て閉じられたが App名がメニューバーに 表示されたままのケースが該当します ではMacのインターフェイスに 最適化するか否かを決めましょう これはAppを移行する際に 考慮すべき 最も大事な決定の 1つです MacでAppを最も快適に感じるには Mac idiomを使うことを推奨しますが それには追加の作業が必要です Mac idiomでは Appは100%スケールで実行され ピクセルパーフェクトな テキストや画像 ネイティブのAppKitコントロールが 得られます 必要に応じて 新しいMac専用アセットを アセットカタログに追加し 更なるディテールに こだわることができます すべてのモニターの 解像度をサポートするために 1倍と2倍の両方のアセットを 用意することをお勧めします 多くのコントロール部品の サイズが変更されることに注意してください Appのレイアウトをそれに対応するよう 調整することが重要です Appのカスタムコントロールには さらなる選択肢があります デフォルトでは Macスタイルのコントロール部品となります しかしボタンやスライダーを 自動変換からオプトアウトし Macのコントロール部品では 利用不可能な カスタマイズAPIを 使うこともできます UISliderのつまみなどに カスタムアセットを使う場合 デフォルトでは予想以上に 大きくなるので 縮小するか 新アセットを用意する必要があります また MacユーザーはAppKitスタイルの コントロールを期待するので カスタムコントロールは あまり使わないようにしましょう Mac idiomについて 詳しくは "Optimize the interface of your Mac Catalyst app."を 参照してください Mac idiom の Catalyst App は AppKitスタイルのとなるで コントロール部品の外観と動作が 変わります 「What's new in Mac Catalyst」 のビデオでは Macタイプのボタンスイートを 完成させる 新しいポップアップボタンスタイルを 紹介しました これらのコントロールの違いと システムが使用するコントロールを 選択する方法について詳しく見てみます これらコントロールを理解し 通常はどこで使われるのかを 知ることで Appでの使用方法について 情報に基づく選択を行えます デフォルトのUIButtonのタイプは .systemです このボタンタイプでは ボタンは自動的に そのコンテキストで期待される 外観になります Mac idiomでは これは枠付きの プッシュボタンになります プルダウンボタンは 適用可能なアクション一覧の 提供に使われる Macネイティブなコントロールで 片矢印のインジケーター付きです これの良い例はプリントダイアログの PDFのプルダウンで PDFに保存やメール送信など アクションを表します プルダウンボタンをつけるには UI button の menu プロパティに UIMenu を割り当て さらに ShowsMenuAsPrimaryAction を true に設定します ボタンがプルダウン風の 外観になり クリックすると メニューを表示します また macOS Monerey の Catalyst の新機能として ポップアップボタンが 追加されました プルダウンボタンに 似ていますが 上下矢印の インジケーターがあり 働きも微妙に 異なります プルダウンがアクションを トリガーすると ポップアップボタンにより オプションのうち1つだけが選択されます 例えばある曜日を選択すると ボタンのタイトルが 選択項目に変更されます Appで使っている UIPickerViewを置き換える Macフレンドリーなチョイスです このコントロールは プルダウンボタンと似ていますが プロパティ changesSelectionAsPrimaryAction が true である必要があります 最後に チェックボックスは 非排他的なバイナリトグルの 表示に使われ スイッチの代わりとして よりマウスフレンドリーなものです このように 追加作業なしで チェックボックスを作れます スイッチには title が セットとなっておりますが title プロパティは Mac idiomでのみ サポートされているので ご注意ください スイッチはデフォルトでは preferredStyle は automatic で 実行時に 読み取り専用の style プロパティを使って スイッチかチェックボックスの どちらで表示されているか 確認することができます ではコードの変更を 詳しく見てみましょう ここでDaveに 交代します こんにちは Daveです UIKitチームの エンジニアです Mac Catalyst Appを よりMacらしくするために できることについて見ていきましょう Mac Catalyst Appはさらに多くの 画面サイズに対応しています Macのウィンドウは iPadのスクリーンよりはるかに大きく リサイズされ また全画面表示も可能です ここでAppのウィンドウをリサイズし レイアウトを観察してみましょう 追加スペースはさらなるコンテンツや コントロールの表示に使って Appを使いやすくしましょう ライブリサイズで Appのレイアウトのパフォーマンスを テストできます リサイズ中Appのウィンドウの レスポンスを維持するため Appでの作業は 最低限にとどめておきましょう モーダル表示や ポップオーバーをよく使うAppでは インタラクションに特に注意します 表示領域を大きくして チャイルドビューとして 表示することで こうしたインタラクションが いつでも利用可能です 次はポインター入力デバイスについてです 全てのMacに トラックパッドがあるとは限らず スクロールできない入力デバイスに 接続されているものもあります ビューでピンチや回転ジェスチャーが 使われている場合 スクロール入力が無いマウスで 全ての機能が利用可能になっているか 確認してください 全ての機能が使えるよう さらなるボタンや その他コントロールを Mac CatalystのAppビューに 追加します さらに タップやパンジェスチャー認識で キーボードの修飾キーを検知することで ビューの機能に よりすばやくアクセスできることもあります 例えば Shift - pan でズームを行います キーボードショートカットと メインメニューを見てみましょう Mac Appのメインメニューでは Appで利用可能な全てのアクションや 関連するキーボードショートカットを 見つけられます Appが既に レスポンダからキーコマンドを返すことで キーボードショートカットに 対応している場合 代わりにメニュービルダーAPIを使って これらのコマンドを追加します 全てのキーボードショートカットを メインメニューに移すと 現在有効でなくても 見つけられるようになります さらに MenuBuilder APIを使って Mac Catalystでショートカットを実装すると それはiPadのショートカット オーバーレイでも表示されます メインメニューを構築する際には Appとのインタラクションに 必要なアクションを 全て追加してください iPadでは ジェスチャーで起動されるアクションも メインメニューでの項目選択から アクセス可能にすべきです キーボードショートカットを メニューアイテムに追加すると こうしたアクションに さらに素早くアクセスできます メニューバーとキーコマンドアクションは ファーストレスポンダからたどるので こうしたアクションの ターゲットになるビューが ファーストレスポンダになり フォーカスを受け取れるように しておきましょう これはビューの canBecomeFirstResponder と canBecomeFocused プロパティで true を返すことで可能です Mac Appでは 画面のタッチ操作は行わず ユーザーはビューを選択後 メインメニューから アクションを選択するので App画面のビューの多くが ファーストレスポンダになり かつフォーカスを得られる事が Mac Catalystでは より重要になります フォーカスとファーストレスポンダに ついて詳しくは 「iPadキーボードナビゲーション」の ビデオを参照してください レスポンダに関してー Appのレスポンダチェーンを 変更しないでください つまり nextResponder は オーバーライドしないでください レスポンダチェーンを そのまま残すことで Mac Catalyst がアクションを 適切なターゲットに ルートするようになります Appがレスポンダチェーンにない オブジェクトで 特定のアクションを処理する時は target(for Action:, withSender:) で こうしたアクションを 適切なオブジェクトに委託します コードで見てみましょう この例では ビューは他のアクションに 引き続きレスポンダチェーンの 伝搬をさせつつ setAsFavoriteアクションを モデルオブジェクトに委託しています では シーンと シーンが Mac Catalyst Appで どう動作するか説明します Mac Appではたくさんの デスクトップウィンドウが 同時に開いていることがあります Mac Catalystではこうしたウィンドウが それぞれ UIWindowScene に 紐づけされています 異なる機能のウィンドウが 表示されるかもしれません 例えばドキュメントウィンドウや 詳細ビューアーメッセージ作成 ウィンドウなどです こうしたシーン機能を整理するには 各ウィンドウタイプのシーン構成を 定義するのがおすすめです シーン構成を定義するには Application Scene Manifest を info.plistに追加します Application Session Role 配列で Appがサポートするシーンのタイプ毎に 1つの configuration を 作成します こうした構成に それぞれ名前を付け シーンクラスと委託クラス そしてシーン作成時に 使われるストーリーボードを 選択します これでシーン構成の 定義が完了したので それを使って 特定の構成の 新しいシーンを作成する 方法を説明します この例では ビューが ダブルクリックされた時に 新しい詳細ビューアーシーンを 作ろうと思います まずは詳細ビューアーシーンを リクエストするための 新しいUser Activityタイプを 定義します これは viewDetailActivityType と呼びます 新しいUser Activityを作成する時 詳細を表示したいアイテムの 識別子を渡します そのためにユーザー情報辞書に その情報を格納する itemIDKeyを定義します 次にイベントハンドラを ダブルクリックして 適切なタイプのNSUserActivity オブジェクトを新しく作り そのUserInfoプロパティに 表示したいitemIDを 辞書で追加します 最後にUIApplication requestSceneSessionActivation 関数を 今作ったUser Activityを指定して 呼び出します これでシステムが新しい シーンを作成します 特定のUser Activityタイプに合わせて 新しいシーンをリクエストする 方法が分かりましたね ではその情報を使って 適切なシーン構成を 読み込むにはどうしたら良いか 説明します シーン作成リクエストに 応答するには Application Delegate の configurationForConnection 関数を 実装します この実装例では 受け取るシーンリクエストに User Activityが含まれているか 確認します 複数のUser Activityが 含まれることがありますが このコード例では 最初のものだけを確認します 処理すべきアクティビティがあった場合 そのactivityTypeをチェックします ここでは viewDetailActivityType に 等しいかをチェックします 等しければ DetailViewer と名付けられた シーンを返します これでこの名前の構成についてシステムが info.plistをチェックし 適切なシーンと委託クラスが読み込まれ 指定されたストーリーボードが 新しいデスクトップウィンドウに 表示されます 特定のシーン構成を ロードする必要がない場合は デフォルト構成に戻ります もう1つすることがあります 表示するアイテムの itemID を 保存しました さっき作成したシーンの ビューコントローラーで 値を設定する必要があります これは SceneDelegate クラスで行います willConnectTo シーンのセッション関数は シーンがデスクトップに表示される 直前に呼び出されます application delegateにパスされた User Actovoty も シーンデレゲートのこの関数へと パスされます これで UserInfo 辞書から itemID を抽出し 新しいビューコントローラーに 設定できるようになりました NSUserActivityで 新しいシーンを構成すると Appの状態復元サポートも より簡単になります シーンデレゲートが stateRestorationActivity(for Scene:) の コールバックに応答すると 返されたUser ActivityはApp終了時に システムによって保存されます 状態復元がSystem Preferences で有効化されると Appが次に起動される時 システムがシーンを再作成し 各シーンのUser Activityオブジェクトを AppDelegate の application configurationForConnecting SceneSession 関数にパスします これは先程触れたAppが新しいシーンを 作るときに呼び出されるのと同じ機能です 一貫したActivityタイプを使用することで Appが新しいデスクトップウィンドウを作る時や 状態復元時に同じコードを使って 適切なシーン構成を選択することができます Appで新しいシーンリクエストと 状態復元の両方を同じコードで処理するには シーンデレゲートにもう1つ 追加するものがあります シーン接続オプションのActivityがnilの場合 シーンデレゲートでシーンのwillConnect セッション関数を修正し stateRestorationActivityに 戻れるようにすることです これでAppで新しいシーンリクエストと 状態復元リクエストの 処理の準備が整いました 状態復元について詳しくは 「Introducing Multiple Windows on iPad」 をご覧ください さて次はAppの ツールバーについてです 優れたMac Appは ウィンドウのツールバーで よく使われるアクションや クイックアクセス用の その他ナビゲーションオプションを 表示します iOSのツールバーと異なり ビューコントローラーが Split Viewコントローラや ナビゲーションコントローラーで 表示・非表示になっても Mac Catalyst Appの デスクトップウィンドウのツールバーは 変更されません ツールバーはシーンと強く 関連付けられているので ツールバーを構成するのは シーンデレゲートのサブクラスが最適です ツールバーにある重要なアイテムの1つに 共有ボタンがあります NSSharingServicePicker と ToolbarItem をツールバーに追加すると Macの標準共有メニューで シーンに表示されるメインコンテンツの 共有が許可されます macOS Montereyでは 共有されるシーンの Activityアイテム構成を 自動的に使う機能を ボタンに追加しました iOS の Siri は同じプロパティを使用した "ShareThis" でデータを共有します シーンに共有構成を提供するには RootViewController の activityItemsConfiguration プロパティから オブジェクトを 返すのがおすすめです Mac Catalystでは Appのツールバーにある NSSharingServicePicker ToolbarItemが このプロパティを 自動的に使用します iOSでは Siriが Share Thisと同じ プロパティを使います もちろん ツールバー以外にも アイテムを提供できる場所はあります コンテキストメニューから 画像やその他アイテムの 共有を許可したい 場合がありますよね これはビューから activityItemsConfiguration オブジェクトを返し contextMenuInteraction を 追加することで可能です Mac CatalystとiPad で 出た結果がこちらです Mac Catalystにはアクションの コピーと共有メニューが 自動的に追加されています そしてiPadでAppが動作すると コピーと共有アクションが 追加されています アクションの共有をタップすると 共有シートが自動的に表示されます Activity Items Configuration APIを使うことで Appでビューが何を共有できるか宣言して 結果システムは各プラットフォームに 適切なUIを表示できます さて Appでデータを共有する方法について 話してきましたが 次は連係カメラを使って iPhone や iPad で Appから画像をインポートする方法です Appでリッチテキスト表示に UITextViewを使う場合 連係カメラサポートは macOS Montereyで 自動的に有効化されています テキストビューで右クリックすると iPhoneやiPadで 「写真を撮る」オプションつきの コンテキストメニューが出てきて それを自動的に添付として 追加します ビューに連係カメラサポートを 追加するには ビューの PasteConfiguration プロパティから 画像を受け取る UIPasteConfiguration オブジェクト を返し UI contextMenuInteraction を 追加するだけです そして paste(itemProviders:) 関数を 実装し 受け取るオブジェクトを 読み込んで貼り付けます ここでは画像ですね ボーナスとして ペースト構成を ビューから返すと 構成で画像を受け取るときに 連係カメラを有効化するだけでなく 貼り付けアクションを コンテキストメニューで 自動的にオンにし ビューがMac CatalystとiPadの両方で そのドラッグを受け取るようにします こうしてAppを優れた Mac Catalyst App に することができるのです では Owen が ディストリビューションについてお話します Dave ありがとう Appを配信する時に 覚えておきたいのは Mac Catalystは Mac Appなので 他のMac Appと 同じ方法で配布できると いうことです Mac App Storeで ユニバーサル購入オプション付きで リリースできるので 既存のiOSユーザーが Mac版を自動的に入手できます Appのベータリリース用 TestFlight を使って 新しいビルドの 早期フィードバックを活用できます また App Notarization を使って 自分で配布することも可能です フレームワークを開発する場合 全プラットフォームの バイナリをまとめた クロスプラットフォーム配信には XCFrameworksを使います 今日は Mac Catalyst で iOS や Mac App を構築する プロセスを説明し その際に行うべき重要な決定や 変更についてお話ししました 今度はご自身のプロジェクト について考えてみてください AppをMacで動作させるのは 簡単ですが 少し工夫をするだけで AppをMacらしくし 素晴らしい新規顧客に 使ってもらえるようにできます ご視聴ありがとう ございます ♪
-
-
6:50 - System button
let button = UIButton(type: .system) button.setTitle("Button", for: .normal)
-
7:06 - Pull-down button
button.menu = UIMenu(...) button.showsMenuAsPrimaryAction = true
-
7:44 - Pop-up button
button.menu = UIMenu(...) button.showsMenuAsPrimaryAction = true button.changesSelectionAsPrimaryAction = true
-
8:24 - Checkbox
let checkbox = UISwitch() if checkbox.style == .checkbox { checkbox.title = "Checkbox" }
-
13:20 - Delegating actions
final class MyView: UIView { override func target(forAction action: Selector, withSender sender: Any?) -> Any? { if action == #selector(Model.setAsFavorite(_:)) { return myModel } else { return super.target(forAction: action, withSender: sender) } } }
-
14:43 - Requesting a new scene
let viewDetailActivityType = "viewDetail" let itemIDKey = "itemID" final class MyView: UIView { @objc func viewDoubleClicked(_ sender: Any?) { let userActivity = NSUserActivity(activityType: viewDetailActivityType) userActivity.userInfo = [itemIDKey: selectedItem.itemID] UIApplication.shared.requestSceneSessionActivation(nil, userActivity: userActivity, options: nil, errorHandler: { error in //... }) } //... }
-
15:57 - Responding to a new scene request
let viewDetailActivityType = "viewDetail" final class AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, configurationForConnecting session: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { if let activity = options.userActivities.first { if activity.activityType == viewDetailActivityType { return UISceneConfiguration(name: "DetailViewer", sessionRole:session.role) } } return UISceneConfiguration(name: "Default Configuration", sessionRole: session.role) } //... }
-
17:13 - Setting item ID on new scene's root view controller
let itemIDKey = "itemID" final class SceneDelegate: UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options: UIScene.ConnectionOptions) { if let userActivity = connectionOptions.userActivities.first { if let itemId = userActivity.userInfo?[itemIDKey] as? ItemIDType { // Set item ID on new view controller } } //... } //...
-
17:47 - Saving state for later restoration
final class SceneDelegate: UIWindowSceneDelegate { func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { //... } }
-
17:57 - State restoration
final class AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, configurationForConnecting session: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { //... } }
-
18:42 - Handle both new scene requests and state restoration
let itemIDKey = "itemID" final class SceneDelegate: UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity { if let itemId = userActivity.userInfo?[itemIDKey] as? ItemIDType { // Set item ID on new view controller } } } }
-
20:20 - Provide sharing configuration for the scene
final class RootViewController: UIViewController { override var activityItemsConfiguration: UIActivityItemsConfigurationReading? { get { UIActivityItemsConfiguration(objects: [image]) } //... } }
-
20:56 - Support sharing through context menu
final class MyView: UIView { override var activityItemsConfiguration: UIActivityItemsConfigurationReading? { get { UIActivityItemsConfiguration(objects: images) } //... } func viewDidLoad() { let contextMenuInteraction = UIContextMenuInteraction(delegate: self) addInteraction(contextMenuInteraction) } }
-
22:08 - Supporting continuity camera
final class MyView: UIView { override var pasteConfiguration: UIPasteConfiguration? { get { UIPasteConfiguration(forAcceptingClass: UIImage.self) } //... } func willMove(toWindow: UIWindow) { addInteraction(contextMenuInteraction) } override func paste(itemProviders: [NSItemProvider]) { for itemProvider in itemProviders { if itemProvider.canLoadObject(ofClass: UIImage.self) { if let image = try? await itemProvider.loadObject(ofClass:UIImage.self) { insertImage(image) } //...
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。