SwiftUIはmacOSアプリのWindow Sizeを変更する手段を提供していない。ViewのLayoutの制御手段が主でWindow管理は別物だ。View.frameモディファイアでWindow Sizeを制御出来るようにも思えるがやってみると上手く行かない。
AppKitのNSWindowにはNSWindow#setFrameがあってWindow Sizeを制御できる。NSWindowインスタンスをContentViewが取得できればNSWindowインスタンスに発生するイベントをdelegate methodで捕まえることもできる。 そこでApp生成後に作られるScene(ContentView)のonAppearモディファイアでNSWindowインスタンス(window)を取得する。window.delegateが受け取るNotificationにはNSWindowインスタンスが入っている。NotificationをContentViewでも同時に受け取る事によりNSWindowインスタンスをContentViewから操作でき、つまりWindow Sizeを変更できる。
- 画像はDefault Sizeボタンを押下することでWindow Sizeを300x300にするアプリのデモ映像
-
App生成コード
import SwiftUI @main struct SimpleWindowApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() .onAppear { // 現在のウィンドウの取得とデリゲート設定 if let window = NSApplication.shared.windows.first { window.delegate = appDelegate } } } } } class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { func windowDidBecomeMain(_ notification: Notification) { if let window = notification.object as? NSWindow { // print("ウィンドウがメインになりました: \(window.title)") print("ウィンドウがメインになりました: \(window.windowNumber)") } } func windowDidChangeTabbingMode(_ notification: Notification) { print("windowDidChangeTabbingMode: ") if let window = notification.object as? NSWindow { if window.tabbingMode == .disallowed { print("ウィンドウが分離されました: \(window.windowNumber)") } else { print("ウィンドウが再度タブに追加されました: \(window.windowNumber)") } } } func windowDidResignMain(_ notification: Notification) { if let window = notification.object as? NSWindow { print("ウィンドウがメインから離れました: \(window.windowNumber)") } } func windowWillClose(_ notification: Notification) { if let window = notification.object as? NSWindow { print("ウィンドウが閉じられました: \(window.windowNumber)") } } }
-
ContentViewのコード
import SwiftUI struct ContentView: View { @State var windowNumber: Int! var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Window #: \(windowNumber ?? 0)") Button("Default Size") { // Get the window whose window number is windowNumber if let window = NSApplication.shared.windows.first( where: { $0.windowNumber == windowNumber }) { let currentFrame = window.frame let newFrame = NSRect(x: currentFrame.minX, y: currentFrame.minY, width: 300, height: 300) window.setFrame(newFrame, display: true) } } } .onReceive(NotificationCenter.default.publisher( for: NSWindow.didResizeNotification)) { e in if let window = e.object as? NSWindow { let contentRect = window.contentLayoutRect if windowNumber == nil { windowNumber = window.windowNumber } // windowNumber = window.windowNumber print("Window resized - content size: \(contentRect.size), Window #: \(window.windowNumber)") } } .onReceive(NotificationCenter.default.publisher( for: NSWindow.didBecomeMainNotification)) { e in if let window = e.object as? NSWindow { let contentRect = window.contentLayoutRect if windowNumber == nil { windowNumber = window.windowNumber } // windowNumber = window.windowNumber print("Become Main - content size: \(contentRect.size), Window #: \(window.windowNumber)") } } .padding() } }
- note: windowNumberが0となる事がある。そこでResizeイベントでもwindowNumberセット
- note: それでもWindowの数が0の時にcommand-NでWindowを生成すると古いwindowNumberが邪魔して0となる。解決策は?