DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Window, Scene & Multi-Window Architecture

As soon as your app goes beyond a single screen, window and scene management becomes real architecture, not boilerplate.

Most SwiftUI apps accidentally:

  • misuse ScenePhase
  • hard-code navigation per window
  • break state when opening multiple windows
  • duplicate ViewModels
  • fight iPad & macOS behavior
  • misunderstand what a scene actually is

This post explains how SwiftUI really manages windows and scenes, and how to architect apps that work correctly on:

  • iPhone
  • iPad (multi-window)
  • macOS
  • visionOS

🧠 1. App vs Scene (The Most Important Distinction)

App

  • Defines what your app is
  • Owns global configuration
  • Creates scenes

Scene

  • Defines how your app is presented
  • Owns window lifecycle
  • Can exist multiple times simultaneously
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Rule:
πŸ“Œ Your app can have multiple scenes, and each scene can have multiple windows.


πŸͺŸ 2. What a WindowGroup Really Does

WindowGroup {
    ContentView()
}
Enter fullscreen mode Exit fullscreen mode

This means:

  • 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

⚠️ 3. The Biggest Multi-Window Bug

This is wrong:

@StateObject var vm = GlobalViewModel()
Enter fullscreen mode Exit fullscreen mode

inside a WindowGroup.

Why?

  • Each window gets a new instance
  • State diverges
  • Navigation desyncs

🧱 4. Correct Global State Placement

Global state must live above scenes:

@main
struct MyApp: App {
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            RootView()
                .environmentObject(appState)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now:

  • all windows share state
  • navigation is consistent
  • data stays in sync

🧭 5. 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.


πŸ”„ 6. ScenePhase Is Per-Scene (Not Global)

@Environment(\.scenePhase) var scenePhase
Enter fullscreen mode Exit fullscreen mode

Each window has its own phase.

That means:

  • backgrounding one window β‰  app background
  • inactive β‰  destroyed
  • active β‰  foreground for all windows

Use this wisely.


🧩 7. Supporting Multiple Scene Types

SwiftUI supports multiple scene roles:

var body: some Scene {
    WindowGroup("Main") {
        MainView()
    }

    WindowGroup("Inspector") {
        InspectorView()
    }

    Settings {
        SettingsView()
    }
}
Enter fullscreen mode Exit fullscreen mode

Use cases:

  • inspector panels
  • settings windows
  • auxiliary tools
  • debug overlays

πŸͺŸ 8. Opening New Windows Programmatically

@Environment(\.openWindow) var openWindow

Button("Open Details") {
    openWindow(id: "details")
}
Enter fullscreen mode Exit fullscreen mode

Define the window:

WindowGroup(id: "details") {
    DetailView()
}
Enter fullscreen mode Exit fullscreen mode

This is how real desktop-class SwiftUI apps work.


🧠 9. Window-Scoped Dependency Injection

Each window should get:

  • its own navigation state
  • shared services
  • shared app state

Example:

WindowGroup {
    RootView(
        router: Router(),
        services: services
    )
}
Enter fullscreen mode Exit fullscreen mode

But:

  • services are shared
  • routers are per-window

πŸ§ͺ 10. Testing Multi-Window Behavior

You must test:

  • opening multiple windows
  • closing windows
  • backgrounding one scene
  • restoring state
  • shared state mutation

Most SwiftUI bugs only appear with two windows open.


πŸš€ Final Thoughts

SwiftUI scenes are not boilerplate β€” they are architecture.

Once you understand:

  • app vs scene
  • window identity
  • scene-local vs global state
  • multi-window behavior

You can build apps that feel:

  • native
  • correct
  • scalable

Top comments (0)