macOS Scenes Reference
macOS Scenes Reference
Section titled “macOS Scenes Reference”SwiftUI scene types for macOS apps —
Settings,MenuBarExtra,WindowGroup,Window,UtilityWindow, andDocumentGroup. Covers macOS-only scenes and cross-platform scenes with macOS-specific behavior.
Table of Contents
Section titled “Table of Contents”- Quick Lookup Table
- Settings (macOS-only)
- MenuBarExtra (macOS-only)
- WindowGroup (macOS behavior)
- Window
- UtilityWindow (macOS-only)
- DocumentGroup
- Platform Conditionals
- Best Practices
Quick Lookup Table
Section titled “Quick Lookup Table”| API | Availability | macOS-Only? | macOS-Specific Behavior |
|---|---|---|---|
WindowGroup |
macOS 11.0+ | No | Multiple window instances, tabbed interface, automatic Window menu commands |
Window |
macOS 13.0+ | No | App quits when sole window closes; adds itself to Windows menu |
UtilityWindow |
macOS 15.0+ | Yes | Floating tool palette; receives FocusedValues from active main window |
Settings |
macOS 11.0+ | Yes | Presents preferences window (Cmd+,) |
MenuBarExtra |
macOS 13.0+ | Yes | Persistent icon/menu in the system menu bar |
DocumentGroup |
macOS 11.0+ | No | Document-based menu bar commands (File > New/Open/Save); multiple document windows |
Settings (macOS-only)
Section titled “Settings (macOS-only)”Presents the app’s preferences window, accessible via Cmd+, or the app menu. SwiftUI automatically enables the Settings menu item and manages the window lifecycle.
Settings { TabView { Tab("General", systemImage: "gear") { GeneralSettingsView() } Tab("Advanced", systemImage: "star") { AdvancedSettingsView() } } .scenePadding() .frame(maxWidth: 350, minHeight: 100)}Use TabView with Tab items for multi-pane preferences. Each tab’s content is typically a Form with @AppStorage-backed controls.
SettingsLink (macOS 14.0+)
Section titled “SettingsLink (macOS 14.0+)”A button that opens the Settings scene. Use for in-app navigation to preferences.
struct SidebarFooter: View { var body: some View { SettingsLink { Label("Preferences", systemImage: "gear") } }}openSettings environment action (macOS 14.0+)
Section titled “openSettings environment action (macOS 14.0+)”Programmatically open (or bring to front) the Settings window.
struct OpenSettingsButton: View { @Environment(\.openSettings) private var openSettings
var body: some View { Button("Open Settings") { openSettings() } }}MenuBarExtra (macOS-only)
Section titled “MenuBarExtra (macOS-only)”Renders a persistent control in the system menu bar. Two styles available:
.menu(default) — standard dropdown menu.window— popover panel with custom SwiftUI views
Menu-style (dropdown)
Section titled “Menu-style (dropdown)”MenuBarExtra("My Utility", systemImage: "hammer") { Button("Action One") { /* ... */ } Button("Action Two") { /* ... */ } Divider() Button("Quit") { NSApplication.shared.terminate(nil) }}Window-style (popover panel)
Section titled “Window-style (popover panel)”MenuBarExtra("Status", systemImage: "chart.bar") { DashboardView() .frame(width: 240)}.menuBarExtraStyle(.window)Variations:
- Toggleable — pass
isInserted:with an@AppStoragebinding to let users show/hide the extra:MenuBarExtra("Status", systemImage: "chart.bar", isInserted: $showMenuBarExtra) - Menu-bar-only app — use
MenuBarExtraas the sole scene + setLSUIElement = truein Info.plist to hide the Dock icon. The app auto-terminates if the user removes the extra from the menu bar.
WindowGroup (macOS behavior)
Section titled “WindowGroup (macOS behavior)”On macOS, WindowGroup supports:
- Multiple window instances — users can open many windows from File > New Window
- Tabbed interface — users can merge windows into tabs
- Automatic Window menu — commands for window management appear automatically
@mainstruct Mail: App { var body: some Scene { // Basic multi-window support WindowGroup { MailViewer() }
// Data-presenting window opened programmatically WindowGroup("Message", for: Message.ID.self) { $messageID in MessageDetail(messageID: messageID) } }}
// Open a specific window programmaticallystruct NewMessageButton: View { var message: Message @Environment(\.openWindow) private var openWindow
var body: some View { Button("Open Message") { openWindow(value: message.id) } }}Key difference from
Window:WindowGroupkeeps the app running even after all windows are closed.Window(as sole scene) quits the app when closed.
Window
Section titled “Window”A single, unique window scene. The system ensures only one instance exists.
@mainstruct Mail: App { var body: some Scene { WindowGroup { MailViewer() }
// Supplementary singleton window Window("Connection Doctor", id: "connection-doctor") { ConnectionDoctor() } }}
// Open programmatically — brings to front if already openstruct OpenDoctorButton: View { @Environment(\.openWindow) private var openWindow
var body: some View { Button("Connection Doctor") { openWindow(id: "connection-doctor") } }}Window as sole scene
Section titled “Window as sole scene”If Window is the only scene, the app quits when the window closes:
@mainstruct VideoCall: App { var body: some Scene { Window("VideoCall", id: "main") { CameraView() } }}Recommendation: In most cases, prefer
WindowGroupfor the primary scene. UseWindowfor supplementary singleton windows.
UtilityWindow (macOS-only)
Section titled “UtilityWindow (macOS-only)”A specialized floating window for tool palettes and inspector panels. Available since macOS 15.0.
Key behaviors:
- Receives
FocusedValuesfrom the focused main scene (like menu bar commands) - Floats above main windows (default level:
.floating) - Hides when the app is no longer active
- Only becomes focused when explicitly needed (e.g., clicking the title bar)
- Dismissible with the Escape key
- Not minimizable by default
- Automatically adds a show/hide item to the View menu
@mainstruct PhotoBrowser: App { var body: some Scene { WindowGroup { PhotoGallery() }
UtilityWindow("Photo Info", id: "photo-info") { PhotoInfoViewer() } }}
struct PhotoInfoViewer: View { // Automatically updates based on whichever main window is focused @FocusedValue(PhotoSelection.self) private var selectedPhotos
var body: some View { if let photos = selectedPhotos { Text("\(photos.count) photos selected") } else { Text("No selection") .foregroundStyle(.secondary) } }}Tip: Remove the automatic View menu item with
.commandsRemoved()and place aWindowVisibilityToggleelsewhere in your commands.
DocumentGroup
Section titled “DocumentGroup”Document-based apps with automatic file management. On macOS, provides:
- Document-based menu bar commands (File > New, Open, Save, Revert)
- Multiple document windows simultaneously
- On iOS, shows a document browser instead
DocumentGroup(newDocument: TextFile()) { config in ContentView(document: config.$document)}The document type must conform to FileDocument (value type) or ReferenceFileDocument (reference type). Key requirements:
struct TextFile: FileDocument { static var readableContentTypes: [UTType] { [.plainText] } var text: String = "" init() {} init(configuration: ReadConfiguration) throws { text = String(data: configuration.file.regularFileContents ?? Data(), encoding: .utf8) ?? "" } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { FileWrapper(regularFileWithContents: Data(text.utf8)) }}For multiple document types, add additional DocumentGroup scenes — use DocumentGroup(viewing:) for read-only formats.
Platform Conditionals
Section titled “Platform Conditionals”Always wrap macOS-only scenes in #if os(macOS):
@mainstruct MyApp: App { var body: some Scene { WindowGroup { ContentView() }
#if os(macOS) Settings { SettingsView() }
MenuBarExtra("Status", systemImage: "bolt") { StatusMenu() } #endif }}Best Practices
Section titled “Best Practices”- Use
Settingsfor preferences — prefer this over a custom preferences window - Use
MenuBarExtrafor menu bar items — prefer this over managing AppKit’sNSStatusItemdirectly - Use
WindowGroupas the primary scene — reserveWindowfor supplementary singletons - Use
UtilityWindowfor inspectors/palettes — it handles floating, focus, and visibility automatically - Use
DocumentGroupfor document-based apps — it provides the full File menu and document lifecycle - Gate macOS-only scenes with
#if os(macOS)for multiplatform projects - Use
openWindow(id:)to open windows programmatically — it brings existing windows to front