WWDC Quick Look 💓 By SwiftGGTeam
Qualities of a great Mac Catalyst app

Qualities of a great Mac Catalyst app

元の動画を見る

ハイライト

優れた Mac Catalyst アプリケーションは、移行中のアーキテクチャ上の決定 (Mac イディオムとスケーリング)、ランタイム エクスペリエンスの最適化 (マルチ ウィンドウ/メニュー/応答チェーン)、および iPad アプリケーションを Mac 上で真に「ネイティブライク」にするためのリリース中の配布戦略の 3 つのレベルに焦点を当てる必要があります。

主な内容

移行の決定: 拡張するか Mac に最適化するか?

iPad アプリを Mac に導入する際の最初の重要な決定は、インターフェイス モードの選択です。Xcode で Mac サポートを確認すると、2 つのオプションが表示されます。

  • 拡大された iPad インターフェイス: iPad インターフェイスは直接拡大され、コントロールは iPad スタイルを維持し、開発作業負荷は最小限に抑えられます。
  • Mac に最適化されたインターフェイス: Mac のイディオムを使用、100% スケーリング、ネイティブ AppKit コントロール、追加の適応が必要

Mac イディオムをお勧めします。ピクセルクリアなテキストと画像、ネイティブの Mac コントロールの外観、および Mac ユーザーの習慣により沿ったインタラクションを実現します。その代償として、新しいコントロールのサイズに合わせてレイアウトを調整する必要があり、カスタム コントロールのスケール変更や新しいマテリアルの提供が必要になる場合があります。

ライフサイクルの違い

Mac Catalyst アプリのライフサイクルは iPad とは異なります。すべてのウィンドウを閉じた後も、アプリケーションがフォアグラウンドで実行されたままになることがあります (アプリケーション名はメニュー バーに表示されたままになります)。sceneDidEnterBackgroundウィンドウが最小化または閉じられたときにのみトリガーされ、iPad よりもはるかに頻度が低くなります。

以前に使用した場合sceneDidEnterBackground自動保存するには、タイマーを使用して定期的に実行する必要があります。

マルチウィンドウとシーンの構成

Mac アプリでは、複数のウィンドウが同時に開いていることがよくあります。異なるシーン構成を定義することにより、ドキュメント ウィンドウ、詳細ウィンドウ、編集ウィンドウなどに独立した構成を作成できます。ダブルクリックして新しいウィンドウを開くことは、Mac ユーザーの標準的な動作です。

##詳細

ネイティブ ボタン コントロール (06:50)

Mac のイディオムでは、UIButton は自動的に Mac スタイルの境界ボタンになります。2 つの属性を組み合わせることで、4 つの標準ボタンを取得できます。

// Push Button — デフォルトスタイル
let pushButton = UIButton(type: .system)
pushButton.setTitle("Button", for: .normal)

// Pull-down Button — クリックでメニューを展開する
let pullDownButton = UIButton(type: .system)
pullDownButton.menu = UIMenu(title: "", children: actions)
pullDownButton.showsMenuAsPrimaryAction = true

// Pop-up Button — 選択後にタイトルが追従して変わる
let popupButton = UIButton(type: .system)
popupButton.menu = UIMenu(title: "", children: actions)
popupButton.showsMenuAsPrimaryAction = true
popupButton.changesSelectionAsPrimaryAction = true

キーポイント:

  • showsMenuAsPrimaryAction = true: Mac の習慣に従って、左クリックしてメニューを直接展開します
  • changesSelectionAsPrimaryAction = true: ボタンのタイトルには現在選択されている項目が自動的に反映されます
  • ポップアップ ボタンの二重矢印インジケーターはプルダウンの単一矢印とは異なり、相互に排他的なオプションの選択に使用されます。
  • タイトルを設定すると、UISwitch が自動的にチェックボックス スタイルに変わります

応答チェーンの委任 (13:20)

Mac アプリは、メニューから操作するために応答性の高いリンクに依存しています。上書きしないでくださいnextResponder、しかし、target(forAction:withSender:)応答チェーンの外側のオブジェクトに操作を委任します。

final class MyView: UIView {
    var myModel: Model!

    override func target(forAction action: Selector, withSender sender: Any?) -> Any? {
        if action == #selector(Model.setAsFavorite(_:)) {
            return myModel
        }
        return super.target(forAction: action, withSender: sender)
    }
}

キーポイント:

  • カバレッジnextResponder応答チェーンが切断され、メニュー ルーティングが失敗します。
  • target(forAction:withSender:)委任する安全な方法です
  • ビューをサポートするようにしますcanBecomeFirstResponderそしてcanBecomeFocused、キーボードのナビゲーションとメニューが正しく動作することを確認します。

マルチウィンドウ シーンの構成 (14:43)

ダブルクリックして新しいウィンドウを開き、シーンを設定する必要があります。

let viewDetailActivityType = "viewDetail"
let itemIDKey = "itemID"

// 1. ダブルクリック時に新しいシーンを要求する
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
                print("Failed to activate scene: \(error)")
            }
        )
    }
}

// 2. AppDelegate で activity に応じて構成を選択する
final class AppDelegate: UIApplicationDelegate {
    func application(_ application: UIApplication,
                     configurationForConnecting session: UISceneSession,
                     options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        if let activity = options.userActivities.first,
           activity.activityType == viewDetailActivityType {
            return UISceneConfiguration(name: "DetailViewer", sessionRole: session.role)
        }
        return UISceneConfiguration(name: "Default Configuration", sessionRole: session.role)
    }
}

// 3. SceneDelegate でデータを設定する
final class SceneDelegate: UIWindowSceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
               options connectionOptions: UIScene.ConnectionOptions) {
        let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity
        if let userActivity = activity,
           let itemId = userActivity.userInfo?[itemIDKey] as? ItemIDType {
            // ルートビューコントローラーに設定する
            (scene as? UIWindowScene)?.rootViewController?.setItemID(itemId)
        }
    }
}

キーポイント:

  • Info.plist のアプリケーション シーン マニフェストで複数のシーン構成を定義する
  • requestSceneSessionActivationシステムをトリガーして新しいウィンドウを作成します
  • configurationForConnectingユーザーアクティビティに応じて、対応するシーン構成を返します。
  • 同時処理connectionOptions.userActivitiesそしてsession.stateRestorationActivity状態回復をサポート

共有機能の統合 (20:20)

// シーンレベルの共有設定
final class RootViewController: UIViewController {
    override var activityItemsConfiguration: UIActivityItemsConfigurationReading? {
        UIActivityItemsConfiguration(objects: [image])
    }
}

// ビューレベルの共有(コンテキストメニュー)
final class MyView: UIView {
    override var activityItemsConfiguration: UIActivityItemsConfigurationReading? {
        UIActivityItemsConfiguration(objects: images)
    }

    func setupContextMenu() {
        let contextMenuInteraction = UIContextMenuInteraction(delegate: self)
        addInteraction(contextMenuInteraction)
    }
}

キーポイント:

  • activityItemsConfigurationMac ツールバー共有ボタンと iOS Siri 共有の両方を提供します
  • Mac Catalyst ツールバー内NSSharingServicePickerToolbarItem設定を自動的に読み取ります
  • コンテキスト メニュー インタラクションを追加すると、システムはコピーと共有のオプションを自動的に追加します。

連続カメラ (22:08)

final class MyView: UIView {
    override var pasteConfiguration: UIPasteConfiguration? {
        UIPasteConfiguration(forAcceptingClass: UIImage.self)
    }

    override func willMove(toWindow newWindow: UIWindow?) {
        super.willMove(toWindow: newWindow)
        let contextMenuInteraction = UIContextMenuInteraction(delegate: self)
        addInteraction(contextMenuInteraction)
    }

    override func paste(itemProviders: [NSItemProvider]) {
        Task {
            for itemProvider in itemProviders {
                if itemProvider.canLoadObject(ofClass: UIImage.self) {
                    if let image = try? await itemProvider.loadObject(ofClass: UIImage.self) {
                        insertImage(image)
                    }
                }
            }
        }
    }
}

キーポイント:

  • UIPasteConfigurationビューで受け入れられる貼り付けタイプを宣言します
  • 画像を受け入れるように設定すると、右クリック メニューに [iPhone からインポート] オプションが自動的に表示されます。
  • メニュー項目の貼り付けとドラッグアンドドロップの同時受付を有効にします
  • UITextView は macOS Monterey 上の Continuity Camera を自動的にサポートします

重要ポイント

  1. やるべきこと: マルチウィンドウ編集用に既存の iPad ドキュメント エディタに Mac Catalyst サポートを追加します。
  • 実行する価値がある理由: Mac ユーザーは、複数のドキュメント ウィンドウを同時に開くことに慣れています。シーン構成を通じて、requestSceneSessionActivationほんの数行のコード
  • 開始方法: Document と DetailViewer の 2 つのシーン構成を定義し、ドキュメント リスト項目がダブルクリックされたときに新しいシーンを要求します。
  1. 機能: Mac 用の写真管理アプリに完全なメニュー バーとキーボード ショートカットを追加します。
  • 実行する価値がある理由: Mac ユーザーはメニュー バーから機能を見つけます。 UIMenuBuilder は、Mac メニュー バーと iPad ショートカット キーのフローティング レイヤーの両方を提供します。
  • 開始方法: を使用しますUIMenuBuilderメニュー構造を構築し、各メニュー項目をバインドしますUIKeyCommand、ビューがサポートしていることを確認してくださいcanBecomeFirstResponder
  1. やるべきこと: Mac の Notes アプリに Continuity カメラのサポートを追加します。
  • 実行する価値がある理由: Mac ユーザーは、最初にコンピュータに転送することなく、iPhone を使用して写真を撮ったり、メモを挿入したりできます。
  • 開始方法: ノート編集ビューでの設定pasteConfigurationUIImageを受け入れて実装するpaste(itemProviders:)
  1. 対処法: Mac バージョンのファイル マネージャーでビューを切り替えるには、セグメント コントロールの代わりにポップアップ ボタンを使用します。
  • 実行する価値がある理由: ポップアップ ボタンは、スペースを節約し、ユーザーの習慣と一致する標準の Mac コントロールです。
  • 開始方法: を使用しますUIButton + changesSelectionAsPrimaryAction + showsMenuAsPrimaryActionさまざまな表示モードに対応するメニュー項目の作成

関連セッション

コメント

GitHub Issues · utterances