SwiftUI Window, Scene & Multi-Window Architecture
Source: Dev.to
App vs Scene (The Most Important Distinction)
App
Defines what your app is, owns global configuration, and creates scenes.
Scene
Defines how your app is presented, owns the window lifecycle, and can exist multiple times simultaneously.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
RootView()
}
}
}
Rule: Your app can have multiple scenes, and each scene can have multiple windows.
What a WindowGroup Really Does
WindowGroup {
ContentView()
}
- iOS: multiple app windows (iPad)
- macOS: multiple windows
- visionOS: multiple spatial instances
Each window:
- has its own view hierarchy
- has its own state
- does not automatically share
ViewModels
The Biggest Multi‑Window Bug
@StateObject var vm = GlobalViewModel()
Placing a @StateObject inside a WindowGroup is wrong because:
- Each window gets a new instance
- State diverges across windows
- Navigation becomes desynchronized
Correct Global State Placement
Global state must live above the scenes:
@main
struct MyApp: App {
@StateObject private var appState = AppState()
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(appState)
}
}
}
Now:
- All windows share the same state
- Navigation is consistent
- Data stays in sync
Scene‑Local State vs. App‑Global State
Scene‑local:
- Navigation stack
- Selection
- Focus
- Scroll position
App‑global:
- Authentication
- User session
- Cache
- Feature flags
- Deep links
Never mix them.
ScenePhase Is Per‑Scene (Not Global)
@Environment(\.scenePhase) var scenePhase
Each window has its own phase, meaning:
- Backgrounding one window ≠ app background
inactive≠ destroyedactive≠ foreground for all windows
Use this information wisely.
Supporting Multiple Scene Types
SwiftUI allows you to declare several scene roles:
var body: some Scene {
WindowGroup("Main") {
MainView()
}
WindowGroup("Inspector") {
InspectorView()
}
Settings {
SettingsView()
}
}
Typical use cases:
- Inspector panels
- Settings windows
- Auxiliary tools
- Debug overlays
Opening New Windows Programmatically
@Environment(\.openWindow) var openWindow
Button("Open Details") {
openWindow(id: "details")
}
Define the window that will be opened:
WindowGroup(id: "details") {
DetailView()
}
This pattern is how desktop‑class SwiftUI apps manage additional windows.
Window‑Scoped Dependency Injection
Each window should receive:
- Its own navigation state
- Shared services
- Shared app state
Example:
WindowGroup {
RootView(
router: Router(),
services: services
)
}
servicesare shared across windowsrouter(or similar navigation objects) are per‑window
Testing Multi‑Window Behavior
When testing, verify that:
- Multiple windows can be opened and closed correctly
- Backgrounding one scene does not affect others
- State restoration works per window
- Shared state mutations propagate as expected
Most SwiftUI bugs only surface when two or more windows are active.
Final Thoughts
SwiftUI scenes are architectural, not boilerplate. Understanding:
- App vs. scene distinction
- Window identity
- Scene‑local vs. global state
- Multi‑window behavior
enables you to build apps that feel:
- Native
- Correct
- Scalable