WWDC Quick Look 💓 By SwiftGGTeam
Discover the Spatial Preview framework

Discover the Spatial Preview framework

观看原视频

Highlight

Spatial Preview 框架让 macOS 应用零 visionOS 代码即可将文档和 3D 内容推送到 Vision Pro 的 Quick Look 中,支持双向实时编辑和 SharePlay 协作。

核心内容

从”再写一套 visionOS App”到”几行代码搞定”

做 3D 内容工具的开发者都经历过这个场景:Mac 上建好了精美的建筑模型或材质,想让客户在 Vision Pro 里沉浸式查看,得再写一个 visionOS 伴侣 App,搞定网络传输、场景同步、手势交互。两套代码、两套资产管线、两套发布流程。

00:27)Spatial Preview 框架直接把这个流程砍到了 Mac 端几行代码。你的 Mac App 创建一个 Session,把文件 URL 或 USD Stage 塞进去,Vision Pro 上的 Quick Look 自动接管渲染和交互。不需要写任何 visionOS 代码,不需要处理设备配对,不需要自己搭网络同步层。

Cinema 4D 和 SketchUp 已经接入了这个能力,实现了材质修改的实时双向同步(01:43)。你在 Mac 上调一下金属度参数,Vision Pro 里的模型立刻跟着变。

两种 Session 覆盖两种场景

框架提供了两类 Session:

  • DocumentPreviewSession:处理空间照片、空间视频、Apple Immersive Video 帧、PDF 等文档类型。
  • USDPreviewSession:处理 3D 内容,基于 USDKit 的 USD Stage 做双向实时编辑。

03:07)选择哪种取决于你的内容类型。做视频剪辑工具的用 DocumentPreviewSession,做 3D 建模或 CAD 工具的用 USDPreviewSession。

设备发现:别假设 Mac Virtual Display 永远在线

框架第一步是获取目标设备端点(Endpoint)。如果用户正在使用 Mac Virtual Display,可以直接拿到已连接的设备。但如果没连,你的 App 需要提供一个设备选择器让用户手动挑选同一 iCloud 账号下的 Vision Pro。

04:20)Apple 提供了现成的 SpatialPreviewDevicePicker SwiftUI 视图,用 Sheet 弹出来就行。不要假设用户永远开着 Mac Virtual Display,否则你的”预览”按钮在没连头显时就是个死按钮。

自动优化的双刃剑

08:32)Vision Pro 的算力虽然强,但渲染 Mac 端几百万面的工业级 USD 场景还是会卡。Spatial Preview 默认开启自动优化,偷偷做网格减面(Mesh Decimation)和纹理降采样,保证帧率。

这对普通预览很友好,但做高精度工业评审的开发者需要注意:减面会导致细节丢失。你可以传 .unmodified 参数关闭优化,但如果场景太复杂,start 方法会直接抛出 assetUnshareable 错误。这意味着你必须在 Mac 端自己做一层资产检查或 LOD 管理,不能无脑把原始资产扔给框架。

SharePlay 协作开箱即用

13:05)Vision OS 端的 SharePlay 支持是内置的。多个协作者可以加入同一个 Live Session,同时查看和编辑同一份空间内容。一个人在 Vision Pro 里移动家具位置,所有人的视图立刻同步更新。

详细内容

启动文档预览 Session

03:58)以下代码展示如何创建 Document Preview Session,通过 Mac Virtual Display 推送到 Vision Pro:

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)
}

@State 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)
            }
        }
    }
}

关键点:

  • ConnectedSpatialEndpointObserver() 监听当前 Mac Virtual Display 连接的设备,返回一个 SpatialPreviewEndpoint
  • DocumentPreviewSession 需要指定内容类型(.aivu 表示 Apple Immersive Video)和会话名称。
  • start(endpoint:) 启动会话后,Vision Pro 的 Quick Look 自动打开。
  • updateContents(url:) 将内容推送到已打开的会话中,复用同一场景。
  • SpatialPreviewDevicePicker 是系统提供的设备选择 UI,用户可从附近同一 iCloud 账号的 Vision Pro 中选择。

更新内容并监听会话状态

05:20)做画廊切换时,用 updateContents 复用场景,而不是每次都新建 Session:

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()

关键点:

  • updateContents(url:) 复用当前场景,避免 Vision Pro 上场景闪烁重建。
  • 如果每次切换文件都重新调用 start,会启动新场景,体验很差。
  • 通过 Observations 监听 session.state,当用户在 Vision Pro 关闭 Quick Look 时,isInvalidated 变为 true,Mac 端可以清理 UI 状态。
  • 会话结束时调用 close(),Vision Pro 上的场景自动关闭。

启动 USD 实时预览

07:36)3D 内容使用 USDKit 加载 USD 场景,再通过 USDPreviewSession 推送:

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)
}

关键点:

  • USDStage.open(stageURL) 用 USDKit 加载本地 USDZ 文件。
  • USDPreviewSession(stage:) 将 Stage 包装成预览会话。
  • start(endpoint:) 启动后,Vision Pro 以体积视图(Volumetric View)展示 3D 内容,用户可切换为沉浸视图(Immersive View)。
  • 相机视角、线框材质覆盖等功能由 Vision Pro 端的 Quick Look 内置提供,Mac 端无需额外代码。

关闭自动优化并处理错误

08:56)高精度场景需要关闭自动优化:

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
}

关键点:

  • .unmodified 参数关闭网格减面和纹理降采样,保留原始精度。
  • 如果场景复杂度超过 Vision Pro 的承载能力,start 抛出 assetUnshareable 错误。
  • 需要在 Mac 端捕获这个错误,给用户降级选项(如加载低模版本)。

使用 USD Variants 切换布局

10:10)USD 的 Variant Sets 可以在 Mac 和 Vision Pro 之间实时同步:

# 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
        }
        ...
    }
}
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)
}

关键点:

  • Variant Sets 是 USD 的标准机制,允许在同一 Prim 上切换不同数据变体。
  • prim.variantSets?.setSelection 切换变体后,Stage 的改动自动同步到 Vision Pro。
  • 在 Vision Pro 的 Quick Look 菜单中也可以切换变体,改动同样回传到 Mac。
  • 适合做室内设计 App,让用户在”现代风”和”原木风”布局间一键切换。

监听 Stage 变化与批注

10:49)监听 Vision Pro 端对 USD Stage 的修改:

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
        }
    }
}

11:13)批注数据需要符合特定 Schema:

AppleTextAnnotation {
    string text
    uniform string author
    uniform string identifier
}

/__documentAnnotationGroup__

关键点:

  • stage.addObserver(for: UsdStage.ObjectsDidChange.self) 订阅 Stage 变化通知。
  • notice.resyncedPaths 包含所有发生变化的 Prim 路径。
  • prim.isAnnotation 判断该 Prim 是否为批注。
  • 批注必须作为 __documentAnnotationGroup 的子 Prim,并符合 AppleTextAnnotation Schema,才能在 Vision Pro 上正确显示。
  • 批注包含 text(内容)、author(作者标识)和 identifier(唯一标识符)三个字段。

启用对象操作与事件监听

11:33)让 Vision Pro 用户能用手势移动 3D 对象,需要在 USD 中设置元数据:

customData = {
    dictionary apple = {
        bool spatialEditable = 1
    }
}

12:16)配置 Session 选项并监听事件:

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)
        }
    }
}

关键点:

  • spatialEditable = 1 的元数据标记让 Prim 在 Vision Pro 上支持手势拖拽移动。
  • options: [.annotations, .perObjectManipulation, .export] 启用批注、对象操作和导出功能,默认全部开启。
  • session.events 提供动画播放时间变化(timeChanged)和播放状态变化(playbackStateChanged)等事件。
  • 可以据此在 Mac 端同步播放控制 UI。

监控同步进度

12:38)大文件传输时展示进度:

import SpatialPreview
import USDKit

@State 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()
    }
}

关键点:

  • session.progress.fractionCompleted 返回 0 到 1 的同步进度。
  • 通过 Observations 监听进度变化,实时更新 UI。
  • 传输几百 MB 的 USDZ 文件时,进度条比转圈动画更能安抚用户。

核心启发

1. 给现有 Mac 3D 工具加一个”空间预览”按钮

做什么:在现有 Mac 建模或设计工具的工具栏加一个”在 Vision Pro 中预览”按钮。

为什么值得做:不需要重写任何 visionOS 代码,几行 Spatial Preview API 就能让产品具备”空间计算”卖点。客户可以在头显里以真实比例查看模型,比屏幕上的 3D 视图直观得多。

怎么开始:引入 SpatialPreviewUSDKit 框架,用 ConnectedSpatialEndpointObserver 获取设备,创建 USDPreviewSession,把当前打开的 USD Stage 传进去。

2. 做一个支持实时协作的室内设计评审工具

做什么:Mac 端放平面图和家具库,Vision Pro 端展示 3D 房间。设计师和客户同时查看,实时讨论布局调整。

为什么值得做:SharePlay 支持是内置的,不需要自己实现多人同步。USD Variants 可以预置几套布局方案,一键切换对比。

怎么开始:用 USDPreviewSessionoptions: [.annotations, .perObjectManipulation] 启用批注和物体移动。在 Mac 端监听 ObjectsDidChange 通知,把 Vision Pro 上的批注和位置改动存进自己的项目数据库。

3. 给视频编辑工具加”空间帧预览”

做什么:从 Apple Immersive Video 中提取关键帧,一键推送到 Vision Pro 查看立体效果。

为什么值得做:DocumentPreviewSession 的 .aivu 类型直接支持 Apple Immersive Video 帧。视频剪辑师可以在剪辑过程中随时检查空间效果,不用导出完整视频再传输到头显。

怎么开始:用 DocumentPreviewSession(name:contentType: .aivu) 创建会话,调用 updateContents(url:) 推送帧文件。画廊视图用 updateContents 复用场景做快速切换。

4. 做一套 USD 资产的”空间批注”工作流

做什么:3D 模型评审时,评审人在 Vision Pro 里直接在模型上钉批注,Mac 端自动收集整理。

为什么值得做:批注以标准 USD Prim 形式存在,不依赖私有格式。你的 Mac App 可以按 authoridentifier 字段把批注归类,生成评审报告。

怎么开始:确保批注 Prim 符合 AppleTextAnnotation Schema,放在 __documentAnnotationGroup 下。Mac 端用 stage.addObserver(for: UsdStage.ObjectsDidChange.self) 监听变化,提取批注数据。

5. 在 Mac 端做性能预警,避免推到 Vision Pro 后报错

做什么:在调用 start 之前,先分析 USD Stage 的面数和纹理大小,给用户一个”此模型可能在 Vision Pro 上性能不佳”的提示。

为什么值得做:关闭自动优化后复杂场景会直接抛 assetUnshareable 错误,体验很差。提前在 Mac 端做检查,可以给用户加载简化版的选项。

怎么开始:用 USDKit 遍历 Stage 中的所有 Mesh Prim,累加面数;检查纹理尺寸。超过阈值时提示用户选择低模版本或开启自动优化。

关联 Session

  • Discover the USDKit framework — USDKit 是 Spatial Preview 处理 3D 内容的基础,了解 USD Stage、Prim 和 Layer 的概念对使用 Spatial Preview 至关重要。
  • What’s new in RealityKit — RealityKit 是 Vision Pro 上渲染 3D 内容的底层框架,Spatial Preview 的 Quick Look 视图基于 RealityKit 构建。
  • Discover Reality Composer Pro 3 — Reality Composer Pro 3 支持在 Mac 上编辑 USD 场景并设置 spatialEditable 元数据,与 Spatial Preview 的工作流直接衔接。
  • Build immersive environments in visionOS — 了解 visionOS 的沉浸视图和体积视图概念,有助于设计 Spatial Preview 内容的展示方式。
  • What’s new in visionOS — visionOS 27 的新特性为 Spatial Preview 提供了 Mac Virtual Display 和 SharePlay 等底层支持。

评论

GitHub Issues · utterances