What are windows, volumes and immersive spaces on visionOS, and how do you build for them with SwiftUI and RealityKit?
Predict first
A scene with a window and an immersive space is built for visionOS and for iOS. What does each compile as?
import SwiftUI
struct A: App {
var body: some Scene {
WindowGroup { Text("x") }
ImmersiveSpace(id: "i") { Text("y") }
}
}
- It compiles on visionOS
- It does not compile on visionOS
Predict first
The same scene built for iOS:
import SwiftUI
struct A: App {
var body: some Scene {
WindowGroup { Text("x") }
ImmersiveSpace(id: "i") { Text("y") }
}
}
- It compiles
- It is an error: ImmersiveSpace is unavailable in iOS
Predict first
The volumetric window style on iOS:
import SwiftUI
struct A: App {
var body: some Scene { WindowGroup(id: "vol") { Text("x") }.windowStyle(.volumetric) }
}
- It compiles
- It is an error: no member volumetric
Mental model. A window is a picture hanging in your living room, a volume is a model on the coffee table you can walk around, and an immersive space is the room itself being redecorated. You start with the picture and only redecorate when the story needs it.
visionOS apps are SwiftUI apps whose scenes live in the user’s surroundings (Shared Space, where several apps coexist, or a Full Space, where one app takes over). There are three kinds of scene. A window (WindowGroup) is a familiar 2D SwiftUI surface floating in space, resizable by the user, with glass material backgrounds, depth-aware controls and ornaments (toolbars attached to its edges); most of an app should be windows, because they work like iPad content and are comfortable to use. A volume (WindowGroup with .windowStyle(.volumetric), and a default size in metres) is a bounded 3D box that holds 3D content (a model, a globe, a game board) the user can walk around, shown with RealityView or Model3D; it sits in the Shared Space next to other apps. An immersive space (ImmersiveSpace(id:)) fills the user’s world with your content: .mixed style keeps passthrough (your content blends with the room), .progressive and .full replace progressively more of it, and only one immersive space can be open, which closes the other apps’ visible content in a Full Space. You open and close them from a window with environment actions (openImmersiveSpace, dismissImmersiveSpace, openWindow), and the volumetric window style and ImmersiveSpace are visionOS-only APIs: the same SwiftUI code fails to compile on iOS with “unavailable in iOS” (or no member volumetric), so keep them in visionOS-specific files or behind #if os(visionOS).
Content and input. 3D content is built with RealityKit (entities, components, systems, materials, anchors, physics and audio), authored with Reality Composer Pro and USDZ assets, placed in a RealityView whose make closure adds entities and whose update closure reacts to state. The default input is eyes and hands: the user looks at something and pinches to select, so interactive elements need hover effects (.hoverEffect()), a generous hit target (around 60 points) and standard controls; 3D entities need InputTargetComponent and collision shapes to receive gestures (.gesture(TapGesture().targetedToAnyEntity())), and the system does not give your app the user’s gaze or camera frames (privacy), though ARKit in a Full Space can provide hand tracking, plane detection and scene meshes with permission. Design guidance: put the user at the centre, keep content in the comfortable field of view, avoid long-lasting immersion without a clear exit and a way to recentre, respect passthrough and safety (never block the room with opaque objects at head height), use spatial audio, minimise motion that can cause discomfort, provide depth cues and shadows, support accessibility (VoiceOver, pointer control, alternative inputs), and test on the device: the visionOS simulator approximates a headset but not comfort or performance. Performance budgets are strict (stable 90 fps; a dropped frame is felt physically), so keep polygon counts, texture memory and shader complexity low. Existing iPad apps can run in a compatible mode, and a shared SwiftUI core with a visionOS leaf is the usual strategy.
ImmersiveSpace is a visionOS scene type, so it compiles for the headset.
Immersive spaces do not exist on iOS, so shared code must keep them out of the iOS build.
The volumetric style is only defined on visionOS.
Common traps
- Opening an immersive space for ordinary content. Most flows suit a window; immersion needs a clear purpose and an easy exit.
- Putting visionOS-only scene APIs in shared code.
ImmersiveSpacedoes not exist on iOS; isolate it in a visionOS file or behind#if os(visionOS).
They might follow up with
- What does the volumetric window style give you? A bounded 3D box in the Shared Space for 3D content.
- How do 3D entities receive gestures? They need an input-target component and collision shapes, then a gesture targeted to entities.
Related questions
- 20.1.2 How do the platforms differ in input, process model and capabilities, and which entitlements and APIs change…
- 20.1.1 How do you structure one codebase for several Apple platforms: shared cores, UI leaves, #if os, canImport and…
- 18.5.3 When do you use SceneKit, RealityKit, SpriteKit or Metal for 3D and games-adjacent work?