SwiftUI macOS App Development: NavigationSplitView and @Observable Guide
macOS SwiftUI development finally works properly on macOS 14+. NavigationSplitView behaves correctly, @Observable replaces the verbose ObservableObject dance, and you can build a full production app without AppKit fallbacks. This guide covers the patterns that work in practice, learned from shipping a production macOS SwiftUI application.
1. macOS 14: The SwiftUI Turning Point
Before macOS 14, building a macOS app in pure SwiftUI was an exercise in frustration. NavigationView had unpredictable behavior on macOS, toolbar items rendered inconsistently, and many views silently fell back to AppKit wrappers that broke in unexpected ways.
macOS 14 (Sonoma) fixed the majority of these issues. NavigationSplitView works as documented. The @Observable macro eliminates boilerplate. Window management APIs are reliable. You can now build a complete macOS app in SwiftUI without AppKit escape hatches for most use cases.
If you are starting a new macOS app in 2026, set your minimum deployment target to macOS 14. The difference in SwiftUI reliability is not incremental - it is a different experience entirely.
3. @Observable vs ObservableObject
The @Observable macro (introduced in macOS 14 / iOS 17) is a massive improvement over the old ObservableObject + @Published pattern. It eliminates the need for @Published property wrappers, reduces boilerplate, and tracks dependencies automatically.
| Feature | ObservableObject | @Observable |
|---|---|---|
| Property marking | @Published on each | Automatic |
| View injection | @ObservedObject / @StateObject | Just pass it |
| Environment | @EnvironmentObject | @Environment |
| Update granularity | Any @Published change | Only accessed properties |
| Boilerplate | High | Minimal |
The update granularity improvement is particularly important for performance. With ObservableObject, any @Published property change re-renders all views that observe the object. With @Observable, only views that actually read the changed property re-render.
4. Central AppState Pattern
The cleanest architecture for macOS SwiftUI apps is a central AppState object marked with @Observable. This object holds the application's shared state and is injected into the view hierarchy via .environment().
This pattern works well because macOS apps typically have shared state that multiple windows need to access - the current document, user preferences, network status, background task progress. A central AppState gives you a single source of truth without the complexity of a full state management framework.
AppState structure:
- - Mark with @Observable (not ObservableObject)
- - Mark with @MainActor for thread safety
- - Inject via .environment() in the App struct
- - Access via @Environment in views
- - Keep it flat - nested objects complicate observation
5. Common macOS SwiftUI Pitfalls
- @MainActor violations: SwiftUI views run on the main actor. Updating view state from a background task without @MainActor causes crashes. Always mark view-facing properties and their containing types with @MainActor.
- Window lifecycle: macOS apps can have multiple windows. State tied to a specific window should use @State, not environment. State shared across windows goes in AppState.
- Menu bar apps: MenuBarExtra works in pure SwiftUI but has sizing quirks. Set a fixed frame on the content view to avoid layout jumps.
- Keyboard shortcuts: Use .keyboardShortcut() on buttons and commands. Do not try to intercept key events manually - it conflicts with the responder chain.
6. When You Still Need AppKit
Even on macOS 14+, some things still require AppKit: custom NSWindow behavior (frameless windows, custom title bars), low-level accessibility API access, certain drag-and-drop patterns, and system-level integrations like status bar items with custom drawing.
Use NSViewRepresentable or NSViewControllerRepresentable to bridge AppKit into SwiftUI when needed. Keep the bridge surface small - wrap the specific AppKit functionality, not entire view hierarchies.
7. Open Source macOS SwiftUI Apps to Study
Studying production macOS SwiftUI apps is the fastest way to learn patterns that work. Here are open-source projects worth examining:
- - Fazm - AI desktop agent built with SwiftUI. Uses NavigationSplitView, @Observable AppState, accessibility API integration, and voice input. Good example of a complex macOS app in pure SwiftUI.
- - CodeEdit - SwiftUI-based code editor for macOS. Complex window management and document handling.
- - Ice - Menu bar management app. Good example of MenuBarExtra usage.
Want to see a production macOS SwiftUI app with accessibility APIs, voice control, and modern patterns? Check out the Fazm source code.
View Source on GitHub