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のビューコントローラーと同様に、サブビューコントローラーとして使用可能です。また、viewプロパティでビューを取得し、サブビューとして配置できます。
addChild(controller)
view.addSubview(controller.view)オートレイアウトの設定
NSHostingControllerは、AppKitのルールにしたがってレイアウトを行う必要があります。そのため、本記事ではAppKitのオートレイアウトを使って、ViewControllerとNSHostingControllerの、幅と高さが同じになるように設定しました。
まず、translateAutoresizingMaskIntoConstraintsプロパティを、falseに変更します。
view.translatesAutoresizingMaskIntoConstraints = falsecontroller.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でウインドウサイズを設定する方法については、次の記事も参照してください。












