macos-app-structure
macOS App Structure
Critical Constraints
- ❌ DO NOT use iOS-only scenes (
TabViewas root scene) → ✅ UseWindowGroup,Window, orNavigationSplitView - ❌ DO NOT use
UIApplicationDelegate→ ✅ UseNSApplicationDelegateAdaptorfor AppKit hooks - ❌ DO NOT forget
Settingsscene for Preferences → ✅ macOS apps should have a Settings scene - ❌ DO NOT assume single-window → ✅ macOS apps can have multiple windows; design for it
- ❌ DO NOT use iOS navigation patterns → ✅ Use
NavigationSplitView(sidebar + detail) for macOS
Standard macOS App
import SwiftUI
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
.defaultSize(width: 900, height: 600)
Settings {
SettingsView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// AppKit lifecycle hooks
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true // Quit when last window closes
}
}
Menu Bar App
@main
struct MenuBarApp: App {
var body: some Scene {
MenuBarExtra("My App", systemImage: "command") {
MenuBarView()
}
.menuBarExtraStyle(.window) // Full window popover (not just menu items)
Settings {
SettingsView()
}
}
}
To hide dock icon, add to Info.plist:
<key>LSUIElement</key>
<true/>
Multiple Named Windows
@main
struct MultiWindowApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
Window("Inspector", id: "inspector") {
InspectorView()
}
.defaultSize(width: 300, height: 400)
.defaultPosition(.trailing)
Settings {
SettingsView()
}
}
}
// Open a named window from code
@Environment(\.openWindow) private var openWindow
Button("Open Inspector") { openWindow(id: "inspector") }
Content View with Sidebar
struct ContentView: View {
@State private var selection: SidebarItem? = .library
var body: some View {
NavigationSplitView {
List(selection: $selection) {
Section("Library") {
Label("All Items", systemImage: "square.grid.2x2")
.tag(SidebarItem.library)
Label("Favorites", systemImage: "heart")
.tag(SidebarItem.favorites)
}
}
.navigationSplitViewColumnWidth(min: 180, ideal: 220)
} detail: {
switch selection {
case .library: LibraryView()
case .favorites: FavoritesView()
case nil: ContentUnavailableView("Select an item", systemImage: "sidebar.left")
}
}
.navigationTitle("My App")
}
}
Project Structure Convention
MyApp/
├── MyApp.swift # @main App struct
├── AppDelegate.swift # NSApplicationDelegateAdaptor (if needed)
├── Models/ # SwiftData @Model classes
├── Views/
│ ├── ContentView.swift # Main navigation structure
│ ├── Components/ # Reusable view components
│ └── Settings/ # Settings/Preferences views
├── ViewModels/ # @Observable view models
├── Services/ # Business logic, networking, persistence
├── Utilities/ # Extensions, helpers
├── Resources/
│ ├── Assets.xcassets
│ └── Localizable.xcstrings
├── Info.plist
└── MyApp.entitlements
Key Info.plist Entries (macOS)
<key>LSUIElement</key> <!-- true = menu bar only, no dock icon -->
<key>NSAccessibilityUsageDescription</key> <!-- Required for Accessibility API -->
<key>NSAppleEventsUsageDescription</key> <!-- Required for AppleScript -->
Key Entitlements
<!-- App Sandbox (required for App Store) -->
<key>com.apple.security.app-sandbox</key><true/>
<!-- Network access -->
<key>com.apple.security.network.client</key><true/>
<!-- File access -->
<key>com.apple.security.files.user-selected.read-write</key><true/>
<!-- iCloud -->
<key>com.apple.developer.icloud-container-identifiers</key>
Common Mistakes & Fixes
| Mistake | Fix |
|---|---|
No Settings scene |
Add Settings { SettingsView() } — expected on macOS |
| App doesn't quit when last window closes | Implement applicationShouldTerminateAfterLastWindowClosed |
| Dock icon showing for menu bar app | Add LSUIElement = true to Info.plist |
| Window too small on macOS | Add .defaultSize(width:height:) to WindowGroup |
Using TabView as main navigation |
Use NavigationSplitView with sidebar on macOS |
References
More from makgunay/claude-swift-skills
macos-permissions
macOS permission handling for Accessibility (AXIsProcessTrusted), Screen Recording, Full Disk Access, input monitoring, camera, microphone, location, and contacts. Covers TCC (Transparency Consent and Control) database, graceful degradation when permissions are denied, permission prompting patterns, opening System Settings to the correct pane, detecting permission changes, and the privacy manifest (PrivacyInfo.xcprivacy) requirement. Use when implementing features that require system permissions, building permission onboarding flows, or handling denied permissions gracefully.
17appkit-bridge
Bridging AppKit components into SwiftUI macOS apps. Covers NSViewRepresentable and NSViewControllerRepresentable protocols for hosting AppKit views in SwiftUI, NSHostingView/NSHostingController for hosting SwiftUI in AppKit, NSPanel for floating windows, NSWindow configuration (styleMask, level, collectionBehavior), responder chain integration, NSEvent monitoring (global and local), NSAnimationContext for AppKit animations, NSPopover, NSStatusItem for menu bar, and NSGlassEffectView for AppKit Liquid Glass. Use when SwiftUI lacks a native equivalent, building floating panels, custom window chrome, or integrating legacy AppKit components.
15swiftui-core
Core SwiftUI patterns for macOS and iOS development including navigation (NavigationSplitView, NavigationStack), state management (@State, @Binding, @Environment, @Bindable with @Observable), the new customizable toolbar system (toolbar IDs, ToolbarSpacer, DefaultToolbarItem, searchToolbarBehavior, matchedTransitionSource, sharedBackgroundVisibility), styled text editing (TextEditor with AttributedString, AttributedTextSelection, transformAttributes, textFormattingDefinition), and layout patterns. Use when building any SwiftUI view, implementing navigation, managing state, creating toolbars, or building rich text editors. Corrects common LLM errors like using deprecated NavigationView, wrong state wrappers, and outdated toolbar APIs.
14global-hotkeys
System-wide keyboard shortcut registration on macOS using NSEvent monitoring (simple, app-level) and Carbon EventHotKey API (reliable, system-wide). Covers NSEvent.addGlobalMonitorForEvents and addLocalMonitorForEvents, CGEvent tap for keystroke simulation, Carbon RegisterEventHotKey for system-wide hotkeys, modifier flag handling (.deviceIndependentFlagsMask), common key code mappings, debouncing, Accessibility permission requirements (AXIsProcessTrusted), and SwiftUI .onKeyPress for in-app shortcuts. Use when implementing global keyboard shortcuts, hotkey-triggered panels, or system-wide key event monitoring.
11swiftui-webkit
Native SwiftUI WebKit integration with the new WebView struct and WebPage observable class. Covers WebView creation from URLs, WebPage for navigation control and state management, JavaScript execution (callJavaScript with arguments and content worlds), custom URL scheme handlers, navigation management (load, reload, back/forward), navigation decisions, text search (findNavigator), content capture (snapshots, PDF generation, web archives), and configuration (data stores, user agents, JS permissions). Use when embedding web content in SwiftUI apps instead of the old WKWebView + UIViewRepresentable/NSViewRepresentable bridge pattern. This is a brand new API — do NOT use the old WKWebView wrapping approach.
10liquid-glass
Comprehensive guide to Apple's Liquid Glass design system introduced in macOS 26, iOS 26, and across all Apple platforms. Covers SwiftUI (.glassEffect(), GlassEffectContainer, .interactive(), .tint(), glassEffectID morphing, .buttonStyle(.glass), .buttonStyle(.glassProminent), glassEffectUnion), AppKit (NSGlassEffectView, NSGlassEffectContainerView), UIKit (UIGlassEffect, UIGlassContainerEffect, UIScrollEdgeEffect), and WidgetKit (accented rendering mode, widgetAccentable). Use whenever building UI with the new Apple design language, adopting glass effects, styling buttons or toolbars, or creating modern macOS/iOS interfaces. Always consult this skill when asked about new Apple design.
10