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.
Declare the scene with an identifier
ImmersiveSpaceis aScene, so it lives in yourAppbody next to your windows. You give it a stringidand a content closure; the system uses thatidto know which space to present. In the example, the button asks to open the space registered under"Galaxy".Open it with the openImmersiveSpace action
Because an immersive space replaces the user's surroundings, you present it explicitly through the
openImmersiveSpaceenvironment action rather than a navigation push. Read it from the environment with@Environment(\.openImmersiveSpace) private var openImmersiveSpace, then call it as anasyncfunction from aTask, as theEnter Spacebutton does withawait openImmersiveSpace(id: "Galaxy").Branch on the open result
Opening can fail or be denied, so
openImmersiveSpacereturns aResultyou switch over. The example checks for.openedto confirm the space is on screen and updatesisOpen = true, falling through todefaultwhen the request did not succeed.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
dismissImmersiveSpaceaction. The example stores it via@Environment(\.dismissImmersiveSpace)and awaitsdismissImmersiveSpace()from theExit Spacebranch, then clearsisOpen.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 isOpenis toggled after each successful open or dismiss, which swaps theButtontitle and the surroundingTextbetween the entered and exited states.
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.
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()
}
}