SwiftUI Window, Scene & Multi-Window Architecture

Published: (December 28, 2025 at 05:42 PM EST)
2 min read
Source: Dev.to

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 ≠ destroyed
  • active ≠ 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
    )
}
  • services are shared across windows
  • router (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
Back to Blog

Related posts

Read more »

SwiftUI Accessibility Internals

Accessibility Is a Parallel View Tree SwiftUI builds two trees: - The visual view tree - The accessibility tree They are related — but not identical. A single...