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 = 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でウインドウサイズを設定する方法については、次の記事も参照してください。