macOS Monterey 12.4 + Xcode 13.4.1時点では、ウインドウ生成に関する処理をSwiftUIで作ろうとすると、WindowGroup
とDocumentGroup
しか選択肢がなく、シングルウインドウアプリは作れません。また、メニューバーを全てアプリ側で定義することもできません。
将来のバージョンでは、これらもSwiftUIだけで実装できるようになると思いますが、現時点(2022年7月10日時点)では、アプリのライフサイクルはAppKitで作るのが現実的です。
この記事では、AppKitで作成したウインドウ上にSwiftUIのビューを表示する処理の作り方を解説します。
Xcodeのテンプレートは?
macOS Big Sur 11以降からSwiftUIで@main
が使用可能になり、Xcode 12ではInterface
からSwiftUI
を選択すると、Life Cycle
からSwiftUI App
とAppKit 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
形式のアプリです。
プロジェクトの作成
プロジェクトを作成します。プロジェクトのオプションでInterface
にStoryboard
を選択します。

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のオートレイアウトを使って、ViewController
とNSHostingController
の幅と高さが同じになるように設定しました。
まず、translateAutoresizingMaskIntoConstraints
プロパティをfalse
に変更します。
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
セグエに指定されています。

このViewController
とNSHostingController
の幅と高さを一致させるように、サンプルコードのようにオートレイアウトを設定すると、ウインドウのサイズは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でウインドウサイズを設定する方法については、次の記事も参照してください。