Windows and commands
Open a purpose-built window
Declare a window with a stable scene identifier and open it from an action without manually managing NSWindow instances.
Not every window shows the same content. Our notes app might want an activity log: one utility window, opened on demand, never duplicated. On the Mac this is normal. On iOS it barely exists as a concept.
Before SwiftUI, this meant creating an NSWindow yourself, keeping a reference to it, positioning it, and remembering not to create a second one. The Window scene replaces all of that with a declaration.
Declare the window
Add it next to your WindowGroup:
@main
struct NotesApp: App {
@StateObject private var model = NotesModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(model)
}
Window("Activity", id: "activity") {
ActivityView()
.environmentObject(model)
}
}
}
Window is the single-instance sibling of WindowGroup. Where WindowGroup happily creates as many windows as the user asks for, Window guarantees at most one. The id is how the rest of the app refers to it.
Open it from a view
Grab the openWindow action from the environment:
struct ContentView: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Show Activity") {
openWindow(id: "activity")
}
}
}
Open it from a menu
A menu command is where this action usually belongs. Environment values are not available on the App struct itself, so wrap the button in a small view:
.commands {
CommandGroup(after: .windowArrangement) {
OpenActivityCommand()
}
}
struct OpenActivityCommand: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Show Activity") {
openWindow(id: "activity")
}
.keyboardShortcut("1", modifiers: [.command, .option])
}
}
Verify the de-duplication
Run it and click the button twice. The first click opens the window. The second brings the existing one to the front instead of spawning a duplicate.
That’s the whole reason the scene has a stable identifier. SwiftUI uses the id to find the scene, and Window uses it to enforce “only one”.
Check the Window menu too. Your Activity window is listed there automatically, and macOS restores it on relaunch if it was open when the user quit.
Pick the right scene type
The mistake to avoid is declaring a WindowGroup for something that should be a Window. Everything appears to work, until a user triggers the open action twice and ends up with two activity logs drifting out of sync.
My rule: if duplicates of a window would ever confuse the user, it’s a Window, not a WindowGroup.
Lesson completed