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 App、または、AppKit App Delegateを選択可能でした。

Xcode 12のプロジェクト作成オプション
Xcode 12のプロジェクト作成オプション

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

Xcode 13のプロジェクト作成オプション
Xcode 13のプロジェクト作成オプション

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のビューコントローラーと同様に、サブビューコントローラーとして使用可能です。また、viewプロパティでビューを取得し、サブビューとして配置できます。

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

オートレイアウトの設定

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

まず、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セグエに指定されています。

Window Controller Sceneのセグエ
Window Controller Sceneのセグエ

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

著書紹介

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Akira Hayashi (林 晃)のアバター Akira Hayashi (林 晃) Representative(代表), Software Engineer(ソフトウェアエンジニア)

アールケー開発代表。Appleプラットフォーム向けの開発を専門としているソフトウェアエンジニア。ソフトウェアの受託開発、技術書執筆、技術指導・セミナー講師。note, Medium, LinkedIn
-
Representative of RK Kaihatsu. Software Engineer Specializing in Development for the Apple Platform. Specializing in contract software development, technical writing, and serving as a tech workshop lecturer. note, Medium, LinkedIn

目次