Set the scene with SwiftUI in visionOS
Design 进阶 2m

Set the scene with SwiftUI in visionOS

2025年6月9日

在 Apple 官方观看视频

一句话判断

visionOS 26 的 SwiftUI scene 系统做了一轮全面升级:窗口可以锁房间、Volume 可以吸墙面和桌面、immersive space 支持 Mac 远程渲染、UIKit 也能桥接 Volume 了。如果你在做 visionOS app,这篇是必读。

这场 Session 讲了什么

Miguel 用一个机器人演话剧的 app(BOTanist 搭配 Shakespeare 舞台)串起了 visionOS 26 的所有新 scene API。

生命周期管理:

  • 房间锁定与恢复: 用户可以把窗口和 Volume 锁定到物理房间,回到那个房间时自动恢复。restorationBehavior(.disabled) 可以禁用不适合持久化的场景(如欢迎页、工具面板)。
  • 启动行为控制: defaultLaunchBehavior 可以指定 app 启动时优先显示哪个窗口。.suppressed 让次要窗口在从 Home 重新启动时不恢复——避免用户回到一个只有工具栏没有主场景的尴尬状态。
  • 唯一窗口:Window(而非 WindowGroup)声明不能被复制的窗口,如视频通话或游戏主窗口。

Volume 增强:

  • 表面吸附: 窗口可以吸到墙面,Volume 可以吸到桌面或地面。SurfaceSnappingInfo API 提供吸附状态和 ARKit 表面分类(需要用户授权世界感知权限)。
  • Presentation 全面开放: popover、sheet、menu、alert、tooltip 都可以在 Volume 内、Volume 的 ornament 上、RealityView 的 attachment 上,甚至直接在 RealityKit 的 PresentationComponent 中使用。支持 presentationBreakthroughEffect 控制被 3D 内容遮挡时的视觉处理。
  • Clipping Margins: preferredWindowClippingMargins 允许在 Volume 边界外渲染内容(仅视觉装饰,不可交互)。用瀑布、云朵等视觉元素扩展场景,不挤压主内容区域。

Immersive Space:

  • 世界重置事件: 长按 Digital Crown 重置坐标系时,onWorldRecenter modifier 通知你的 app。
  • Progressive immersion 调整: 支持 portrait aspect ratio(适合纵向体验或从 iPhone 移植的游戏)。范围可自定义,建议不要包含 0%(否则内容可能完全消失)。
  • Mixed immersion 与系统环境共存: immersiveEnvironmentBehavior(.coexist) 让 mixed immersive space 的内容在系统环境中也能显示。
  • RemoteImmersiveSpace: Mac 上的 app 可以把 immersive content 通过 CompositorLayer 流式渲染到 Vision Pro。RemoteDeviceIdentifier 传给 ARKitSession 连接远端设备。
  • CompositorContent builder: CompositorLayer 现在可以访问 SwiftUI 的 environment 变量和 modifier,不再是孤岛。

Scene Bridging(场景桥接):

  • UIKit app 通过 UIHostingSceneDelegate 桥接 SwiftUI 的 Volume 和 ImmersiveSpace。Safari 的 Spatial Browsing 就是这么做的。AppKit 也有对应的 API。

值得深挖的点

Surface snapping + restoration 的联动逻辑。 吸附到表面的窗口会被自动锁定并持久化。如果你的 secondary window 不应该被锁定,务必要设 restorationBehavior(.disabled)

Clipping Margins 不是保证给你的。 系统可能只 grant 部分 margin 甚至不给。代码必须读 windowClippingMargins environment 变量,用 min(granted, desired) 来适配。

RemoteImmersiveSpace 目前只支持 progressive 和 full immersion style。 而且只有 CompositorLayer 能在远端渲染,普通的 SwiftUI View 不行。

Scene Bridging 是给存量 UIKit app 的增量升级路径。 不需要重写整个 app,只要创建一个 UIHostingSceneDelegate 子类声明 SwiftUI scenes,然后通过 UISceneSessionActivationRequest 激活就行。

代码片段

Volume 吸附到桌面后隐藏底座:

@State private var showPlatform = true

var body: some View {
    StageView()
        .environment(\.surfaceSnappingInfo, snappingInfo)
        .onChange(of: snappingInfo) { _, newValue in
            if newValue.isSnapped,
               authorizationStatus == .authorized,
               newValue.surfaceClassification == .table {
                showPlatform = false
            }
        }
}

Clipping Margins 渲染瀑布:

.preferredWindowClippingMargins(.bottom, maxHeight)

// 读取实际授予的 margin
@Environment(\.windowClippingMargins) var margins

WaterfallView()
    .frame(height: min(margins.bottom, waterfallHeight))

UIKit 桥接 SwiftUI Volume:

class StageHostingDelegate: UIHostingSceneDelegate {
    override var rootScene: some Scene {
        StageVolume()
    }
}

// 激活
let request = UISceneSessionActivationRequest(
    role: .windowApplication,
    sessionRole: .windowApplication,
    hostingDelegateClass: StageHostingDelegate.self
)
UIApplication.shared.activateSceneSession(request)

最佳实践

优先使用 restoration。 大多数窗口都应该支持锁定和恢复,用户期望如此。只对明确的临时场景(欢迎页、一次性操作)禁用。

Secondary window 用 .suppressed 避免意外恢复。 这比 restorationBehavior 更有效,因为它还防止了从 Home 启动时的意外恢复。

Volume 中的 Presentation 要考虑 3D 遮挡。 默认 subtle blending 可能不够醒目,根据内容重要性选择 prominentnone

Surface snapping 需要权限。 如果需要 ARKit 表面分类信息,在 Info.plist 中设置 Application Wants Detailed Surface InfoPrivacy World-Sensing Usage-Description

还有什么值得关注

  • visionOS 26 新增 widget 支持,widget 可以吸附到墙面或桌面。
  • onVolumeViewpointChange 可以响应用户在 Volume 周围走动时的视角变化,配合 clipping margins 做视线友好的遮挡。
  • RemoteImmersiveSpace 配合 CompositorContent,现在可以在 Mac 上做完整的 Metal immersive app 然后流式投射到 Vision Pro。
  • 配套视频:“What’s new in Metal rendering for immersive apps”(Session 294)详细讲解了 RemoteImmersiveSpace 的 CompositorLayer 适配。
Design 空间计算 SwiftUI