TechnologiesSwiftUI

ImmersiveSpace struct

iOSmacOStvOSwatchOSvisionOS✓ renders

A scene that presents its content in an unbounded space.

How it works

An ImmersiveSpace is a scene that places your app's content in the space around the wearer, removing the bounded window frame so views can be positioned anywhere in the surrounding environment. You declare it alongside your WindowGroup in the app's body, give it a unique identifier, and then open or close it on demand rather than at launch. Reach for ImmersiveSpace when an experience needs to escape a flat window — surrounding scenes, spatial layouts, or RealityKit content that envelops the viewer.

  1. Declare the scene with an identifier

    ImmersiveSpace is a Scene, so it lives in your App body next to your windows. You give it a string id and a content closure; the system uses that id to know which space to present. In the example, the button asks to open the space registered under "Galaxy".

  2. Open it with the openImmersiveSpace action

    Because an immersive space replaces the user's surroundings, you present it explicitly through the openImmersiveSpace environment action rather than a navigation push. Read it from the environment with @Environment(\.openImmersiveSpace) private var openImmersiveSpace, then call it as an async function from a Task, as the Enter Space button does with await openImmersiveSpace(id: "Galaxy").

  3. Branch on the open result

    Opening can fail or be denied, so openImmersiveSpace returns a Result you switch over. The example checks for .opened to confirm the space is on screen and updates isOpen = true, falling through to default when the request did not succeed.

  4. Dismiss it with dismissImmersiveSpace

    Only one immersive space can be open at a time, and you take the user back to bounded windows by calling the matching dismissImmersiveSpace action. The example stores it via @Environment(\.dismissImmersiveSpace) and awaits dismissImmersiveSpace() from the Exit Space branch, then clears isOpen.

  5. Track presentation state

    Neither action mutates your view directly, so you mirror the space's status yourself to drive the UI. Here @State private var isOpen is toggled after each successful open or dismiss, which swaps the Button title and the surrounding Text between the entered and exited states.

Try it — Change the id passed to openImmersiveSpace(id: "Galaxy") to a name your app does not declare and watch the result fall to the default case so isOpen stays false and the space never opens.

Example & preview

Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.

ImmersiveSpace.swift
struct ImmersiveSpaceDemo: View {
    @Environment(\.openImmersiveSpace) private var openImmersiveSpace
    @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
    @State private var isOpen = false

    var body: some View {
        VStack(spacing: 16) {
            Text("Immersive Space")
                .font(.title2.bold())
            Text(isOpen ? "Surrounding content is presented" : "Tap to enter the immersive scene")
                .font(.callout)
                .foregroundStyle(.secondary)
                .multilineTextAlignment(.center)
            Button(isOpen ? "Exit Space" : "Enter Space") {
                Task {
                    if isOpen {
                        await dismissImmersiveSpace()
                        isOpen = false
                    } else {
                        switch await openImmersiveSpace(id: "Galaxy") {
                        case .opened: isOpen = true
                        default: isOpen = false
                        }
                    }
                }
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}
Live preview
Immersive Space Tap to enter the immersive scene Enter Space
Immersive Space Tap to enter the immersive scene Enter Space
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →