.agents/skills/swiftui-expert-skill/references/macos-scenes.md
SwiftUI scene types for macOS apps —
Settings,MenuBarExtra,WindowGroup,Window,UtilityWindow, andDocumentGroup. Covers macOS-only scenes and cross-platform scenes with macOS-specific behavior.
| 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 |
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.
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")
}
}
}
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()
}
}
}
Renders a persistent control in the system menu bar. Two styles available:
.menu (default) — standard dropdown menu.window — popover panel with custom SwiftUI viewsMenuBarExtra("My Utility", systemImage: "hammer") {
Button("Action One") { /* ... */ }
Button("Action Two") { /* ... */ }
Divider()
Button("Quit") { NSApplication.shared.terminate(nil) }
}
MenuBarExtra("Status", systemImage: "chart.bar") {
DashboardView()
.frame(width: 240)
}
.menuBarExtraStyle(.window)
Variations:
isInserted: with an @AppStorage binding to let users show/hide the extra: MenuBarExtra("Status", systemImage: "chart.bar", isInserted: $showMenuBarExtra)MenuBarExtra as the sole scene + set LSUIElement = true in Info.plist to hide the Dock icon. The app auto-terminates if the user removes the extra from the menu bar.On macOS, WindowGroup supports:
@main
struct 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 programmatically
struct 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.
A single, unique window scene. The system ensures only one instance exists.
@main
struct 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 open
struct OpenDoctorButton: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Connection Doctor") {
openWindow(id: "connection-doctor")
}
}
}
If Window is the only scene, the app quits when the window closes:
@main
struct VideoCall: App {
var body: some Scene {
Window("VideoCall", id: "main") {
CameraView()
}
}
}
Recommendation: In most cases, prefer
WindowGroupfor the primary scene. UseWindowfor supplementary singleton windows.
A specialized floating window for tool palettes and inspector panels. Available since macOS 15.0.
Key behaviors:
FocusedValues from the focused main scene (like menu bar commands).floating)@main
struct 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.
Document-based apps with automatic file management. On macOS, provides:
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.
Always wrap macOS-only scenes in #if os(macOS):
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
#if os(macOS)
Settings {
SettingsView()
}
MenuBarExtra("Status", systemImage: "bolt") {
StatusMenu()
}
#endif
}
}
Settings for preferences — prefer this over a custom preferences windowMenuBarExtra for menu bar items — prefer this over managing AppKit's NSStatusItem directlyWindowGroup as the primary scene — reserve Window for supplementary singletonsUtilityWindow for inspectors/palettes — it handles floating, focus, and visibility automaticallyDocumentGroup for document-based apps — it provides the full File menu and document lifecycle#if os(macOS) for multiplatform projectsopenWindow(id:) to open windows programmatically — it brings existing windows to front