How do windows, volumes and immersive spaces fit together in SwiftUI on visionOS: ornaments, openImmersiveSpace, immersion styles, and what only compiles there?

20.9.3 · L3 · Interview

Predict first

Does this visionOS app with a window, a volume and an immersive space type-check for visionOS?

import SwiftUI
import RealityKit
struct Demo: App {
    @State private var style: ImmersionStyle = .mixed
    var body: some SwiftUI.Scene {
        WindowGroup(id: "main") { Launcher() }
        WindowGroup(id: "globe") { Text("Globe") }.windowStyle(.volumetric).defaultSize(width: 0.5, height: 0.5, depth: 0.5, in: .meters)
        ImmersiveSpace(id: "space") { RealityView { content in content.add(Entity()) } }
            .immersionStyle(selection: $style, in: .mixed, .progressive, .full)
    }
}
struct Launcher: View {
    @Environment(\.openImmersiveSpace) private var open
    @Environment(\.dismissImmersiveSpace) private var dismiss
    var body: some View {
        Button("Enter") { Task { _ = await open(id: "space") } }
            .glassBackgroundEffect()
            .ornament(attachmentAnchor: .scene(.bottom)) { Button("Exit") { Task { await dismiss() } } }
            .hoverEffect()
    }
}
  • Yes
  • No

Predict first

Does an app that declares an ImmersiveSpace type-check for iOS?

import SwiftUI
struct Demo: App {
    var body: some Scene { ImmersiveSpace(id: "space") { Text("x") } }
}
  • Yes
  • No, unavailable in iOS

Mental model. A window is a picture on your wall, a volume is a model on your table, an immersive space is the room itself being repainted; the system lets only one room be repainted at a time.

The scene vocabulary is SwiftUI’s with three additions. A WindowGroup is a 2D window (use glassBackgroundEffect() for the system material and .ornament(attachmentAnchor:) to attach controls outside its edge). A WindowGroup with .windowStyle(.volumetric) and defaultSize(width:height:depth:in:) in meters is a volume. An ImmersiveSpace(id:) hosts content in a RealityView, with .immersionStyle(selection:in:) choosing among .mixed, .progressive and .full. You open and close it with the environment actions openImmersiveSpace and dismissImmersiveSpace (async, so call from a Task). The type-check of that whole app compiled for visionOS, and the same ImmersiveSpace is “unavailable in iOS” (and in macOS, tvOS, watchOS, as the earlier matrix showed).

Design points that the compiler cannot check but interviewers ask: only one immersive space is open at a time, and opening another needs the first to be dismissed; a window can stay open in a space; system hand and eye input arrive as ordinary gestures and hover effects (hoverEffect() for views, HoverEffectComponent and InputTargetComponent for entities) because the app does not receive raw gaze data; hand-tracking providers need an ARKitSession authorization and an open immersive space; and each scene type carries its own privacy and comfort expectations. Keep visionOS-only scenes behind #if os(visionOS) in the leaf and make the shared view model independent of them. Not run: the visionOS simulator (none is installed here), so these results are compile-time only, and nothing about presentation, gaze or hand tracking was exercised.

All the scene and modifier APIs exist on visionOS.

Immersive spaces are visionOS only.

Common traps

  • Calling openImmersiveSpace while another space is open. Dismiss first; treat it as one-at-a-time.
  • Putting ImmersiveSpace in a shared App. iOS and macOS builds fail; use #if os(visionOS).

They might follow up with

  • How do 3D entities receive taps? InputTargetComponent plus a collision shape, and a gesture targeted to the entity.
  • When is a volume better than a window? When content is a 3D object people should look around.

Related questions

  • 20.6.1 What are windows, volumes and immersive spaces on visionOS, and how do you build for them with SwiftUI and…
  • 20.9.1 Which SwiftUI scene types exist on which platform (Settings, MenuBarExtra, Window, DocumentGroup…
  • 20.7.1 What are os(), canImport() and targetEnvironment() actually true for on each Apple platform, and where does…

← Which common view modifiers are platform specific…   How do a Swift package’s platforms declaration and its dependants… →