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.
12 minute lesson
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 app behavior. On iOS it barely exists as a concept.
Before SwiftUI, this meant creating and retaining an NSWindow yourself, positioning it, and remembering not to create a second one. The Window scene replaces all of that with a declaration:
@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.
To 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")
}
}
}
You can trigger the same action from a menu command, which is where it usually belongs. Environment actions 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])
}
}
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 de-duplication is 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.
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. If duplicates of a window would ever confuse the user, it is a Window, not a WindowGroup.
Lesson completed