2025 DesignSpatial ComputingSwiftUI & UI Frameworks
WWDC25 · 25 min · Design / Spatial Computing / SwiftUI & UI Frameworks
Set the scene with SwiftUI in visionOS
Discover exciting new APIs to enhance windows, volumes, and immersive spaces in your visionOS app. Fine tune the behavior of your scenes when relaunched or locked in place. Make volumes adapt to their surroundings with clipping margins and snapping. Stream immersive content from Mac to Vision Pro. Elevate your existing UIKit-based apps with volumes and immersive spaces.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 13 snippets
Disabling restoration
// Disabling restoration
WindowGroup("Tools", id: "tools") {
ToolsView()
}
.restorationBehavior(.disabled) Disabling restoration in UIKit
// Disabling restoration
windowScene.destructionConditions = [
.systemDisconnection
] Specifying launch window
// Specifying launch window
("isFirstLaunch") private var isFirstLaunch = true
var body: some Scene {
WindowGroup("Stage Selection", id: "selection") {
SelectionView()
}
WindowGroup("Welcome", id: "welcome") {
WelcomeView()
.onAppear {
isFirstLaunch = false
}
}
.defaultLaunchBehavior(isFirstLaunch ? .presented : .automatic)
// ...
} "suppressed" behavior
// "suppressed" behavior
WindowGroup("Tools", id: "tools") {
ToolsView()
}
.restorationBehavior(.disabled)
.defaultLaunchBehavior(.suppressed) Unique window
// Unique window
("isFirstLaunch") private var isFirstLaunch = true
var body: some Scene {
// ...
Window("Welcome", id: "welcome") {
WelcomeView()
.onAppear {
isFirstLaunch = false
}
}
.defaultLaunchBehavior(isFirstLaunch ? .presented : .automatic)
WindowGroup("Main Stage", id: "main") {
StageView()
}
// ...
} Surface snapping
// Surface snapping
(\.surfaceSnappingInfo) private var snappingInfo
private var hidePlatform = false
var body: some View {
RealityView { /* ... */ }
.onChange(of: snappingInfo) {
if snappingInfo.isSnapped &&
SurfaceSnappingInfo.authorizationStatus == .authorized
{
switch snappingInfo.classification {
case .table:
hidePlatform = true
default:
hidePlatform = false
}
}
}
} Clipping margins
// Clipping margins
(\.windowClippingMargins) private var windowMargins
(from: .meters) private var pointsPerMeter = 1
var body: some View {
RealityView { content in
// ...
waterfall = createWaterfallEntity()
content.add(waterfall)
} update: { content in
waterfall.scale.y = Float(min(
windowMargins.bottom / pointsPerMeter,
maxWaterfallHeight))
// ...
}
.preferredWindowClippingMargins(.bottom, maxWaterfallHeight * pointsPerMeter)
} World recenter
// World recenter
var body: some View {
RealityView { content in
// ...
}
.onWorldRecenter {
recomputePositions()
}
} Progressive immersion style
// Progressive immersion style
private var selectedStyle: ImmersionStyle = .progressive
var body: some Scene {
ImmersiveSpace(id: "space") {
ImmersiveView()
}
.immersionStyle(
selection: $selectedStyle,
in: .progressive(aspectRatio: .portrait))
} Mixed immersion style
// Mixed immersion style
private var selectedStyle: ImmersionStyle = .progressive
var body: some Scene {
ImmersiveSpace(id: "space") {
ImmersiveView()
}
.immersionStyle(selection: $selectedStyle, in: .mixed)
.immersiveEnvironmentBehavior(.coexist)
} Remote immersive space
// Remote immersive space
// Presented on visionOS
RemoteImmersiveSpace(id: "preview-space") {
CompositorLayer(configuration: config) { /* ... */ }
}
// Presented on macOS
WindowGroup("Main Stage", id: "main") {
StageView()
} 'CompositorLayer' is a 'CompositorContent'
// 'CompositorLayer' is a 'CompositorContent'
struct ImmersiveContent: CompositorContent {
(\.scenePhase) private var scenePhase
var body: some CompositorContent {
CompositorLayer { renderer in
// ...
}
.onImmersionChange { oldImmersion, newImmersion in
// ...
}
}
} Scene bridging
// Scene bridging
import UIKit
import SwiftUI
// Declare the scenes
class MyHostingSceneDelegate: NSObject, UIHostingSceneDelegate {
static var rootScene: some Scene {
WindowGroup(id: "my-volume") {
ContentView()
}
.windowStyle(.volumetric)
}
}
// Create a request for the scene
let requestWithId = UISceneSessionActivationRequest(
hostingDelegateClass: MyHostingSceneDelegate.self, id: "my-volume")!
// Send a request
UIApplication.shared.activateSceneSession(for: requestWithId) Resources
Related sessions
-
25 min -
24 min -
30 min