What’s new in Quick Look for visionOS
Spatial Computing 进阶 20m

visionOS 中 Quick Look 的新功能

What’s new in Quick Look for visionOS

2024年6月10日

在 Apple 官方观看视频

一句话判断

visionOS 2 的 Quick Look 拿到了全新的 PreviewApplication API,几行代码就能在你的 app 里嵌入窗口化的空间媒体预览,加上 3D 模型的 Surface Mapping 和 Configurations 支持,这是做 visionOS 内容消费类应用必看的一场。

这场 Session 讲了什么

Quick Look 是 Apple 全平台通用的文件预览和编辑工具,在 visionOS 上因为 3D 内容的存在有了独特的价值。visionOS 上预览 3D 内容有两种模式:内嵌式(作为全屏 cover 嵌入你的 app)和窗口式(在独立的 volume 中展示,支持多任务)。今年 Apple 推出了全新的 PreviewApplication API,基于 SwiftUI 和 Swift Concurrency 构建,目标是让你用最少代码实现最大控制。

这个 API 做了几件关键的事:可以预览单个文件,也可以打开一组文件的集合视图;可以自定义默认的编辑选项(比如关闭裁剪功能);提供了直观的方式来管理预览文件的生命周期。Demo 中展示了一个旅行分享 app,点击缩略图就能在窗口化的 Quick Look 中播放空间视频,同时还能继续操作 app 本身。

在 3D 内容方面,两个呼声最高的功能终于来了。Surface Mapping 让任何 3D 模型都可以吸附到桌面或地板等水平面上,拖动窗口栏靠近桌面就能触发吸附,而且吸附后可以自由在桌面上滑动模型。Configurations 则允许在一个 USDZ 文件中包含多个变体(比如不同颜色的 iPhone),用户可以在 Quick Look 中直接切换,不用为每个颜色单独准备文件。

值得深挖的点

PreviewApplication API 的会话生命周期管理

这个 API 最精妙的设计是会话(Session)概念。当你调用 PreviewApplication.open 时,返回值是一个 session 实例,它的 events 属性是一个 Swift AsyncSequence。你可以监听这个流来获知预览窗口何时打开、何时关闭。

这意味着什么?你可以在 UI 上精确地反映预览状态——比如用一个小眼睛图标表示某个文件正在 Quick Look 中预览,关闭后图标自动消失。更关键的是,API 保证了同一个文件只会有一个预览实例。重复打开同一文件不会创建新窗口,而是把已有的预览带到前台。这个去重逻辑帮你省去了手动管理窗口状态的麻烦。

从架构角度看,这是一个很好的 Swift Concurrency 应用案例:用 AsyncSequence 替代传统的 delegate 回调,代码更清晰,也更容易和现有的 async/await 流程组合使用。

Surface Mapping 的设计细节

Surface Mapping 的实现比看上去复杂得多。它自动为所有 3D 模型启用,无需额外代码。但背后有几个值得注意的约束:模型吸附到平面后,pitch 旋转会被禁用,防止模型穿透桌面。为了让吸附效果自然,模型的底部需要放在原点位置——如果你的模型导出时底部偏离了原点,吸附后就会出现”悬浮”或”陷入桌面”的问题。

这其实反映了空间计算中一个反复出现的主题:物理世界的规则会反过来约束你的数字内容。在传统屏幕上,模型的坐标原点无关紧要;但在空间计算中,这个原点决定了你的模型和真实世界的关系。

代码片段

使用 PreviewApplication 打开空间视频预览

import QuickLook

// 打开单个文件的窗口化预览
struct ThumbnailImage: View {
    let fileURL: URL
    @State private var session: PreviewApplication.Session?
    @State private var isPreviewOpen = false
    
    var body: some View {
        Image(systemName: "photo")
            .onTapGesture {
                // 使用 PreviewApplication 打开窗口化 Quick Look
                Task {
                    session = await PreviewApplication.open(url: fileURL)
                    observeSession()
                }
            }
            // 根据预览状态显示指示器
            .overlay {
                if isPreviewOpen {
                    Image(systemName: "eye")
                        .opacity(0.6)
                }
            }
    }
    
    // 监听预览的打开和关闭事件
    private func observeSession() {
        guard let session else { return }
        Task {
            for await event in session.events {
                switch event {
                case .opened:
                    isPreviewOpen = true
                case .closed:
                    isPreviewOpen = false
                }
            }
        }
    }
}

场景:在旅行 app 中点击缩略图预览空间视频。坑点:session 必须被强引用持有,如果被释放了事件流就会中断。

自定义预览项的显示名称和编辑模式

// 使用 PreviewItem 自定义标题和禁用编辑功能
let previewItem = PreviewItem(
    url: selectedURL,
    displayName: "Galapagos Trip",  // 顶部菜单显示的标题
    editingMode: .disabled            // 禁用裁剪等编辑选项
)

Task {
    session = await PreviewApplication.open(previewItem: previewItem)
}

场景:你希望控制用户在 Quick Look 中能做什么,比如只允许查看不允许编辑。坑点:editingMode 除了 .disabled 还有其他选项,需要查阅文档确认哪种模式适合你的场景。

打开文件集合视图

// 打开一组文件,聚焦到选中的那个
let allURLs = entry.files.map { $0.url }  // 当前条目的所有文件
Task {
    session = await PreviewApplication.open(
        urls: allURLs,
        selectedURL: selectedFile.url  // 集合视图首先展示这个文件
    )
}

场景:旅行 app 中一次预览某个条目下的所有空间视频和照片。坑点:集合视图的导航箭头体验很好,但文件数量太多时需要考虑加载性能。

最佳实践

  • 优先使用窗口化预览:在 visionOS 上,窗口化 Quick Look 让用户可以同时操作你的 app 和预览内容,内嵌式预览会打断工作流。
  • 3D 模型底部对齐原点:为了让 Surface Mapping 吸附效果正确,确保导出的 USDZ 模型底部落在坐标原点上。
  • 合理设置编辑模式:如果你的 app 不需要用户在 Quick Look 中编辑文件,主动设置 editingMode: .disabled,避免用户误操作修改原始文件。
  • 利用 session 事件管理 UI 状态:通过监听 session 的打开/关闭事件,在 UI 上同步反映预览状态,让用户清楚知道哪些文件正在查看中。
  • 空间媒体专项参考:配合《Building compelling spatial photo and video experiences》Session 一起看,了解如何为 app 创建空间照片和视频内容。

还有什么值得关注

  • Configurations 功能让一个 USDZ 文件包含多个变体成为可能,对于电商类应用展示不同颜色/款式的产品非常有用。
  • Quick Look 的编辑结果默认会回写到原始文件,如果你的 app 有自己的文件管理逻辑,需要注意这个行为。
  • 通过 drag and drop 获取窗口化 Quick Look 的旧方式仍然可用,但新的 PreviewApplication API 提供了更精细的控制能力。
WWDC 2024