How to change immersive style at runtime
Our apps can switch between mixed, progressive, and full immersion.
Overview
When we define an Immersive Space in our app, we set an immersive style. For example, a full space can use a constant selection and provide .full for the list of styles.
- selection: the currently selected immersive style. This can be a constant or a binding
ImmersiveSpace(id: "Garden") {
...
}
.immersionStyle(selection: .constant(.full), in: .full)Let’s add a value to the App Model so we can modify it from ContentView.
@Observable
class AppModel {
...
var immersiveStyle: ImmersionStyle = .mixed
}Then we’ll update the Immersive Space. We’ll use a binding for selection and provide a list of styles. If you try to use a selection that isn’t in your list, you will run into issues.
.immersionStyle(selection: $appModel.immersiveStyle, in: .mixed, .full, .progressive)Next we can add buttons to ContentView to change the selection.
VStack {
Text("Immerive Style:")
HStack {
Button(action: {
appModel.immersiveStyle = .mixed
}, label: {
Text("Mixed")
})
Button(action: {
appModel.immersiveStyle = .progressive(0.0...1.0, initialAmount: 0.3)
}, label: {
Text("Progressive")
})
Button(action: {
appModel.immersiveStyle = .full
}, label: {
Text("Full")
})
}
}We may want to adapt our scene content based on the selected style. For example, in .mixed mode we could show only 3D models, but in .progressive or .full mode we could add an environments or skybox. This can be a bit tricky, but it is doable. ImmersiveStyle is not equitable, so we can’t use it in onChange or a Switch statement. Instead, we can add something like this to the RealityView update closure.
if appModel.immersiveStyle is MixedImmersionStyle {
// adjust scene for mixed mode
} else if appModel.immersiveStyle is FullImmersionStyle {
// adjust scene for full mode
} else if appModel.immersiveStyle is ProgressiveImmersionStyle {
// adjust scene for progressive mode
}Note: when we switch modes like this, we can often see some jarring flashes. In a production app, it may be best to fade out any content before making a transition like this, then fade it back in.
Video Demo
Video demo showing a scene switching from mixed to progressive to full immersion
Example Code
Sample code is available in Garden031 in the Step Into Example Projects repo.
App
struct Garden031App: App {
@State private var appModel = AppModel()
var body: some Scene {
WindowGroup {
ContentView()
.environment(appModel)
}
.defaultSize(width: 400, height: 400, depth: 400)
ImmersiveSpace(id: appModel.immersiveSpaceID) {
ImmersiveView()
.environment(appModel)
.onAppear {
appModel.immersiveSpaceState = .open
}
.onDisappear {
appModel.immersiveSpaceState = .closed
}
}
// the value for immersiveStyle is defined in the App Model so we can easily change it from the ContentView
.immersionStyle(selection: $appModel.immersiveStyle, in: .mixed, .full, .progressive)
}
}App Model
@MainActor
@Observable
class AppModel {
let immersiveSpaceID = "ImmersiveSpace"
enum ImmersiveSpaceState {
case closed
case inTransition
case open
}
var immersiveSpaceState = ImmersiveSpaceState.closed
var immersiveStyle: ImmersionStyle = .mixed
}Content View
struct ContentView: View {
@Environment(AppModel.self) private var appModel
var body: some View {
VStack(spacing: 24) {
Text("Immersive Garden 031")
.font(.largeTitle)
Text("Changing immersive style")
ToggleImmersiveSpaceButton()
VStack {
Text("Immerive Style:")
HStack {
Button(action: {
appModel.immersiveStyle = .mixed
}, label: {
Text("Mixed")
})
Button(action: {
appModel.immersiveStyle = .progressive(0.0...1.0, initialAmount: 0.3)
}, label: {
Text("Progressive")
})
Button(action: {
appModel.immersiveStyle = .full
}, label: {
Text("Full")
})
}
}
.padding(.vertical)
}
.padding()
}
}Immersive Space
struct ImmersiveView: View {
@Environment(AppModel.self) private var appModel
var body: some View {
RealityView { content in
// Add the initial RealityKit content
if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
content.add(immersiveContentEntity)
}
} update: { content in
guard let skyDome = content.entities.first?.findEntity(named: "SkyDome") else { return }
guard let pillars = content.entities.first?.findEntity(named: "Pillars") else { return }
if appModel.immersiveStyle is MixedImmersionStyle {
skyDome.isEnabled = false
pillars.isEnabled = false
} else if appModel.immersiveStyle is FullImmersionStyle {
skyDome.isEnabled = true
pillars.isEnabled = true
} else if appModel.immersiveStyle is ProgressiveImmersionStyle {
skyDome.isEnabled = true
pillars.isEnabled = false
}
}
}
}See also:
- How to open and dismiss immersive spaces in visionOS
- Explore immersive styles for spaces in visionOS
- How to respond to changes in immersion level
Support our work so we can continue to bring you new examples and articles.
Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Follow Step Into Vision