Understanding scenes for your macOS app

Understanding scenes for your macOS app

Learn how to manage scenes and windows within a SwiftUI app.

When you build a macOS app using SwiftUI, you design a set of scenes. Each scene represents a portion of the user interface that the system can show. On macOS, scenes map to windows (or other kinds of UI hosts) and manage lifecycle, state restoration, and platform integration.

Let's get through an overview of the standard scene types you are likely to use when developing for macOS.

The standard multi-window container

WindowGroup is the primary scene type for building window-based interfaces in SwiftUI. On macOS, it serves as a template for creating one or more windows that share the same structural layout but maintain their own independent state.

Users can open additional windows from the system menu. Selecting File → New Window will open a new instance of your main WindowGroup and if you provide a WindowGroup with a title you will also have the option File → New [Title] Window on the system menu.

Basic WindowGroup

WindowGroup {
    ContentView()
}

Every time the system or your code creates a new window from this group, SwiftUI initializes a fresh instance of the view it contains, along with new @State and @StateObject values associated with it. This makes WindowGroup ideal for the main interface of the application and any UI that should support multiple simultaneous windows.

Data-driven WindowGroup

Sometimes you need context-oriented windows, for example, a window that displays a specific instance of a model. For this, SwiftUI provides a data-driven form of creating a WindowGroup:

WindowGroup(for: MyModel.self) { model in
    ModelEditorView(model: model)
}

In this variant, each window is bound to a specific instance of your data type. SwiftUI ensures a one-window-per-value relationship: if you try to open a window for the same model twice, it will simply bring the existing window to the front rather than creating duplicates.

To open a data-bound window programmatically, use openWindow(value:):

struct ListView: View {
    @Environment(\.openWindow) private var openWindow

    var body: some View {
        Button("Open Project") {
            openWindow(value: selectedProject)
        }
    }
}

This is extremely powerful for apps that operate on identifiable items, such as editors, document managers, task planners, note apps, chat rooms, and more, because it lets SwiftUI manage window identity, state restoration, and window reuse for you.

A single window

Window is a scene type useful when you want a distinct, single-instance window that you control, for example, an inspector panel, a utility view, or a window that should only open programmatically.

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            MainView()
        }

        Window("Inspector", id: "inspector") {
            InspectorView()
        }
    }
}

Then somewhere in your UI you can open that window by refering to its id value:

struct MainView: View {
    @Environment(\.openWindow) private var openWindow

    var body: some View {
        Button("Show Inspector") {
            openWindow(id: "inspector")
        }
    }
}

Secondary utility panels

SwiftUI includes UtilityWindow, a scene type designed for lightweight, always-available supporting panels, similar to what pro Mac apps use for small inspectors or floating tool palettes.

UtilityWindow("Shortcuts") {
    ShortcutsView()
}

When you add one to your macOS app, your user will have a menu item to toggle its visibility in the View menu.


Document-based apps

If your app revolves around opening, editing, and creating documents, DocumentGroup is the SwiftUI native way to integrate with the macOS file system, opening, editing and saving documents.

struct MyDocument: FileDocument {
    // implement required methods...
}

@main
struct DocumentApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: MyDocument()) { file in
            DocumentEditorView(document: file.$document)
        }
    }
}

Because DocumentGroup integrates with macOS file/document APIs and menus, the user gets familiar behaviors (open/save panels, recent files, multiple document windows, etc.).

If your app is sandboxed (common when distributing via the Mac App Store), you must ensure the correct entitlements in the Signing & Capabilities App Sandbox (e.g. allowing “User-Selected File → Read/Write”) if your app writes to user-selected documents.


Settings: standard macOS preferences window

For macOS apps that need a preferences/settings panel, Settings is the built-in scene type. SwiftUI uses it to automatically wire up the “Settings” menu item (⌘ + ,) in the app menu.

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }

        Settings {
            SettingsView()
        }
    }
}

MenuBarExtra is a scene type introduced for macOS that lets your app live (or partly live) in the menu bar, as a status-item or menu-bar item that the user interacts with.

Its usage fits best for utility apps, quick-access tools, or “always-available” menu-bar helpers.

@main
struct MyMenuBarApp: App {
    var body: some Scene {
        MenuBarExtra("MyApp", systemImage: "star") {
            MenuBarContentView()
        }
        .menuBarExtraStyle(.menu)
    }
}

You can optionally combine MenuBarExtra with other scenes (e.g. WindowGroup, DocumentGroup, etc.), depending on whether your app has a windowed UI in addition to the menu-bar interface.

With the .menuBarExtraStyle(style: MenuBarExtraStyle) modifier, the MenuBarExtra can either appear as a default .menu with a simple list of commands, or as a .window, rendering the UI exactly as you want it to appear.