SwiftUIで使うウインドウをAppKitで作る

macOS Monterey 12.4 + Xcode 13.4.1時点では、ウインドウ生成に関する処理をSwiftUIで作ろうとすると、WindowGroupDocumentGroupしか選択肢がなく、シングルウインドウアプリは作れません。また、メニューバーを全てアプリ側で定義することもできません。

将来のバージョンでは、これらもSwiftUIだけで実装できるようになると思いますが、現時点(2022年7月10日時点)では、アプリのライフサイクルはAppKitで作るのが現実的です。

この記事では、AppKitで作成したウインドウ上にSwiftUIのビューを表示する処理の作り方を解説します。

スポンサーリンク

Xcodeのテンプレートは?

macOS Big Sur 11以降からSwiftUIで@mainが使用可能になり、Xcode 12ではInterfaceからSwiftUIを選択すると、Life CycleからSwiftUI AppAppKit App Delegateを選択することができました。

しかし、Xcode 13ではLife Cycleがプロジェクトオプションダイアログから削除され、InterfaceからSwiftUIを選択すると、常にSwiftUI App方式のコードが作成されるようになりました。

SwiftUI Appが十分な機能を提供できていればそれでも構わないのですが、macOS 12 Montereryの時点では不十分です。例えば、次のようなことができません。

  • シングルウインドウスタイルのアプリを作れない。
  • メニューバーのカスタマイズが一部のみ。
  • Document Appでファイルを直接扱う形の実装ができない。

macOSアプリを作成するための機能強化は将来のmacOSで行われていくと思いますが、現時点ではAppKit App Delegate形式で作るのが現実的です。

この記事で作成するのは、Xcode 12で提供されていたようなAppKit App Delegate形式のアプリです。

プロジェクトの作成

プロジェクトを作成します。プロジェクトのオプションでInterfaceStoryboardを選択します。

SwiftUIのビューを配置する

SwiftUIのビューをViewControllerに配置します。使用するのは、NSHostingControllerクラスです。NSHostingControllerはSwiftUIのビューを配置することができるビューコントローラです。

ViewControllerのコードを次のように変更します。

import Cocoa
import SwiftUI

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.translatesAutoresizingMaskIntoConstraints = false

        // NSHostingController作成
        let controller = NSHostingController(rootView:
        Text("This is a SwiftUI View.")
            .frame(width: 200, height: 200, alignment: .center)
        )
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        
        // ViewControllerに配置する
        addChild(controller)
        view.addSubview(controller.view)

        // オートレイアウトで幅と高さをViewControllerと同じになるように設定する
        NSLayoutConstraint.activate([
            controller.view.leftAnchor.constraint(equalTo: view.leftAnchor),
            controller.view.topAnchor.constraint(equalTo: view.topAnchor),
            controller.view.widthAnchor.constraint(equalTo: view.widthAnchor),
            controller.view.heightAnchor.constraint(equalTo: view.heightAnchor)
        ])

    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }


}
スポンサーリンク

SwiftUIのビューの生成

SwiftUIのビューはNSHostingControllerのイニシャライザの引数rootViewに指定します。任意のビューを使用可能です。ここでは、Textを使いましたが、通常はアプリ側で実装した独自のビューを指定します。

let controller = NSHostingController(rootView:
    Text("This is a SwiftUI View.")
        .frame(width: 200, height: 200, alignment: .center)
)

NSHostingControllerの配置

NSHostingControllerはSwiftUIフレームワークによって提供されるAppKitのビューコントローラです。NSViewControllerクラスを継承しているので、他のAppKitのビューコントローラと同様に、サブビューコントローラ、及び、サブビューとして配置します。

addChild(controller)
view.addSubview(controller.view)

オートレイアウトの設定

NSHostingControllerはAppKitのルールでレイアウトを行う必要があるので、ここでは、AppKitのオートレイアウトを使って、ViewControllerNSHostingControllerの幅と高さが同じになるように設定しました。

まず、translateAutoresizingMaskIntoConstraintsfalseに変更します。

view.translatesAutoresizingMaskIntoConstraints = false
controller.view.translatesAutoresizingMaskIntoConstraints = false

次に、オートーレイアウトを設定します。

NSLayoutConstraint.activate([
    controller.view.leftAnchor.constraint(equalTo: view.leftAnchor),
    controller.view.topAnchor.constraint(equalTo: view.topAnchor),
    controller.view.widthAnchor.constraint(equalTo: view.widthAnchor),
    controller.view.heightAnchor.constraint(equalTo: view.heightAnchor)
])

実行結果

このサンプルコードを実行すると、次のようにウインドウが表示されます。

ウインドウのサイズ

ViewControllerはウインドウのwindow contentセグエに指定されています。

このViewControllerNSHostingControllerの幅と高さを一致させるように、サンプルコードのようにオートレイアウトを設定すると、ウインドウのサイズはSwiftUIのビューの指定に従います。

例えば、このサンプルコードのように以下のコードにすると、ViewControllerの幅と高さが200pxになり、ウインドウはそれに合わせたサイズになります。

let controller = NSHostingController(rootView:
    Text("This is a SwiftUI View.")
        .frame(width: 200, height: 200, alignment: .center)
)

macOS Montereyでは幅200px、高さ228pxのウインドウになり、サイズ変更もできません。これを次のように変更します。

let controller = NSHostingController(rootView:
Text("This is a SwiftUI View.")
    .frame(minWidth: 100, idealWidth: 200, maxWidth: 300,
           minHeight: 100, idealHeight: 200, maxHeight: 500, alignment: .center)
)

すると、ウインドウサイズは可変になり、最小サイズが100px * 100px、最大サイズが300px * 500pxとなります。

SwiftUIでウインドウサイズを設定する方法については、次の記事も参照してください。

関連記事

SwiftUIでmacOSアプリを作っていて、ウインドウサイズの初期値や最大値、最小値を設定する方法について解説します。 WindowGroupを使っているとき WindowGroupを使っているコードでウインドウを定義するコ[…]

スポンサーリンク
最新情報をチェックしよう!