The WindowManagement package allows you to control window behaviors of SwiftUI Scenes without doing weird tricks. Views can also access the NSWindow through the Environment.
This package uses and modifies some internals of SwiftUI. As SwiftUI changes frequently, implementations might break.
Note: DocumentGroup is not supported (yet)
- macOS 12.0 (Untested, but should work)
- macOS 13.0
- macOS 14.0
Using SwiftUI windows can be difficult when parts of your app rely on AppKit types. For example, you can't open a SwiftUI window from an AppDelegate
. This package adds some functions to NSApp
to allow this kind of behavior.
First, define a SceneID for each SwiftUI scene:
extension SceneID {
static let myWindow = SceneID("myWindow")
}
Important: you must add .enableOpenWindow()
to one scene (only one is required) to enable the functionality.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup(id: SceneID.myWindow.id) {
...
}
.enableOpenWindow()
}
}
You can open a window by calling the following function:
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.openWindow(.myWindow)
}
You can also open a SwiftUI Settings window (also works on macOS Sonoma):
func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.openSettings()
}
var body: some Scene {
Group {
WindowGroup {
...
}
Settings {
...
}
NSDocumentGroup(for: ...) { _ in
...
}
}
.environment(\.controlSize, .large)
}
This modifier enables the modification of the underlying NSWindow
for this scene.
It should always be called before any of the other modifiers.
The identifier must be the same as the one set in the Scene initializer.
For the Settings
Scene, use registerSettingsWindow
.
func register(_ identifier: String)
func register(_ identifier: SceneID)
func registerSettingsWindow()
Indicates whether the window can be moved by clicking and dragging its background.
func movableByBackground(_ value: Bool)
Indicates whether the view can be moved.
func movable(_ value: Bool)
Apply a certain stylemask to the window.
Note that NSWindow.StyleMask.titled
is always included, otherwise SwiftUI will crash.
func styleMask(_ styleMask: NSWindow.StyleMask)
Indicated whether a window button should be enabled or not.
If disabled, the button will be greyed out but still visible.
The keyboard shortcut of the button won't be active.
Use windowButton(_:hidden:)
to hide a window button.
func windowButton(_ button: NSWindow.ButtonType, enabled: Bool)
Indicated whether a window button should be hidden or not.
A hidden button can still be triggered with it's keyboard shortcut, e.g. Cmd+w for closing a window.
Use windowButton(_:hidden:)
to disable a window button.
func windowButton(_ button: NSWindow.ButtonType, hidden: Bool)
Apply a certain collectionBehavior to the window.
func collectionBehavior(_ behavior: NSWindow.CollectionBehavior)
Set the tabbingMode for the window. If set to preferred, new windows will be opened as tabs.
func tabbingMode(_ mode: NSWindow.TabbingMode)
Sets the background color of the window.
func backgroundColor(_ color: NSColor)
Makes the titlebar transparent.
func titlebarAppearsTransparent(_ value: Bool)
This will stop windows from relaunching if they were open in the last active app state.
Note: While this should work fine, I can't guarantee it.
func disableRestoreOnLaunch()
Configure the NSWindow.AnimationBehavior for the Scene
func transition(_ transition: NSWindow.AnimationBehavior)
Inject the current window into the environment.
func injectWindow(_ identifier: String)
Inject the settings window into the environment.
func injectSettingsWindow()
Get the NSWindow from a View.
@Environment(\.window) var window
NSDocumentGroup is an alternative to SwiftUI's documentgroup. It allows you to use a SwiftUI Window with an NSDocument. This is useful for cases where you need SwiftUI (for example, to make .focusedValue
work), but FileDocument
or ReferenceFileDocument
are not meeting your requirements. One of the issues I ran into with these types is that they become slow when opening large folders (for example, a node_modules folder). Using an NSDocument allows you to optimize this.
See the example folder for a working sample project.
First, create a NSDocument class (make sure to add the filetype to the project config).
Override the makeWindowControllers()
type and open a new SwiftUI window by calling openDocument
.
override func makeWindowControllers() {
if let window = NSApp.openDocument(self), let windowController = window.windowController {
addWindowController(windowController)
}
}
Add a new NSDocumentGroup Scene to your app. The scene provides a reference to the opened NSDocument.
NSDocumentGroup(for: CodeFileDocument.self) { document in
Text(document.fileURL?.absoluteString ?? "")
}
Make SwiftUI Materials always active
Note: Set this once in the initializer of your implementation of the
App
protocol
NSWindow.alwaysUseActiveAppearance = true
An example project can be found in the project repository.