2026 Graphics & GamesSpatial Computing
WWDC26 · 15 min · Graphics & Games / Spatial Computing
Discover the Spatial Preview framework
Check out how the new Spatial Preview framework brings content from your Mac directly into visionOS. Discover how to build dynamic workflows with live-syncing and bidirectional editing across both platforms. Learn about the SpatialPreview API, device discovery, 2D and 3D session integration, and new Quick Look capabilities to elevate your Mac apps spatially.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 11 snippets
Document Preview Session with Device Picker
// Send and update documents using the Spatial Preview framework
import SwiftUI
import SpatialPreview
let deviceObserver = ConnectedSpatialEndpointObserver()
let previewSession = DocumentPreviewSession(name: "Immersive.aivu", contentType: .aivu)
func startPreview(contentURL: URL, endpoint: SpatialPreviewEndpoint) async throws {
let endpoint = try await deviceObserver.endpoint
try await previewSession.start(endpoint: endpoint)
try await previewSession.updateContents(url: contentURL)
}
var showDevicePicker: Bool = false
var body: some View {
...
.sheet(isPresented: $showDevicePicker) {
SpatialPreviewDevicePicker(isPresented: $showDevicePicker) { endpoint in
showDevicePicker = false
Task {
try await startPreview(filename: filename, endpoint: endpoint)
}
}
}
} Update Document Contents
// Send and update documents using the Spatial Preview framework
import SwiftUI
import SpatialPreview
ForEach(contentURLs, id: \.self) { url in
Button {
Task { try await previewSession?.updateContents(url: url) }
}
}
.task(id: previewSession.map { ObjectIdentifier($0) }) {
for await state in Observations({ session.state }) {
if state.isInvalidated {
previewSession = nil
break
}
}
}
try await previewSession?.close() Edit USD Live
// Edit USD live using USDKit and Spatial Preview
import SpatialPreview
import USDKit
let deviceObserver = ConnectedSpatialEndpointObserver()
var usdSession: USDPreviewSession?
func shareStage(to endpoint: SpatialPreviewEndpoint) async throws -> USDPreviewSession {
let endpoint = try await deviceObserver.endpoint
let stageURL = Bundle.main.url(forResource: "sampleScene", withExtension: "usdz")
let stage = try USDStage.open(stageURL)
usdSession = USDPreviewSession(stage: stage)
try await usdSession?.start(endpoint: endpoint)
} Opt out of optimization
// Optimization
import SpatialPreview
let endpoint = try await deviceObserver.endpoint
do {
try await usdSession.start(endpoint: endpoint, parameters: .unmodified)
} catch USDPreviewSession.Error.assetUnshareable {
// Handle Asset Unshareable error
} USD Layout Variants
// LayoutVariants.usda
#usda 1.0
over "furniture" (
variantSets = "Layout"
variants = { string Layout = "LayoutA" }
)
{
variantSet "Layout" = {
"LayoutA" {
// Default furniture position and rotation
}
"LayoutB" {
// Moves furniture prims to a different position and rotation
}
...
}
} Edit USD live using USDKit and Spatial Preview
// Edit USD live using USDKit and Spatial Preview
import SpatialPreview
import USDKit
func applyLayoutVariant(named layoutVariantName: String) throws {
let prim = stage.prim(at: SdfPath("/root/furniture"))
try prim.variantSets?.setSelection("Layout", variantName: layoutVariantName)
} USD Stage Observations
// Edit USD live using Spatial Preview
import SpatialPreview
import USDKit
let observerToken: ObservationToken
observerToken = stage.addObserver(for: UsdStage.ObjectsDidChange.self) { notice in
for path in notice.resyncedPaths {
let prim = notice.stage.prim(at: path)
guard prim.isValid else { continue }
if prim.isAnnotation {
// Handle annotation change
break
}
}
} Annotation Spec
// Annotation spec example
AppleTextAnnotation {
// The textual representation of this annotation
string text
// The identifier for this specific author
uniform string author
// An identifier that is unique to your data tracking system
uniform string identifier
}
/__documentAnnotationGroup__ Metadata for Object Manipulation
// Metadata required for object manipulation in Quick Look
customData = {
dictionary apple = {
bool spatialEditable = 1
}
} Session Options and Events
// Spatial Preview session options and events
import SpatialPreview
import USDKit
session.start(endpoint: endpoint, options: [.annotations, .perObjectManipulation, .export])
func listenForEvents(session: USDPreviewSession) async {
for await event in session.events {
if case .timeChanged(let time) = event {
playbackModel.timeCode = time
} else if case .playbackStateChanged(let isPlaying) = event {
playbackModel.playbackStateChanged(isPlaying)
}
}
} Observe Session Progress
// Observe Spatial Preview session progress
import SpatialPreview
import USDKit
private var sessionProgress: Double = 0
var body: some View {
...
.task(id: usdSession.map { ObjectIdentifier($0) }) {
guard let session = usdSession else { return }
for await fraction in Observations({ session.progress.fractionCompleted }) {
sessionProgress = fraction
}
}
.overlay(alignment: .bottom) {
ProgressView(value: sessionProgress)
.padding()
}
} Resources
Related sessions
-
25 min -
15 min -
18 min