WWDC Quick Look 💓 By SwiftGGTeam
Build document-based apps in SwiftUI

Build document-based apps in SwiftUI

观看原视频

Highlight

SwiftUI 的 DocumentGroup 让 App 用一个 Scene 声明文档打开与管理能力,并由系统提供文档浏览器、标准文档命令、macOS 状态跟踪与 Handoff、iOS 文档浏览和分享界面。


核心内容

用户在 Finder 和 Files app 里整理文件,会用标签、云文件提供方和外接存储设备。他们打开一个文件时,期待 App 直接编辑原始文档;如果 App 只是把文件导入自己的数据库,用户改到的是副本,原始文件不会跟着变。

这类能力不只属于专业创作工具。Pixelmator、Keynote 和 Final Cut Pro 的主要工作都围绕文档管理展开;Xcode、Mail、Console 也会在主界面之外打开工程、EML 文件或崩溃报告。关键点是打开并编辑文件本身,而不是把文件吞进 App 私有数据里。

SwiftUI 把这个能力放进 DocumentGroup。App 仍然由 App、Scene 和 View 组成;当 Scene 换成 DocumentGroup,你声明的是:这个 App 支持打开和管理某类文档。SwiftUI 接着补上平台行为,包括 macOS 的状态跟踪与 Handoff,以及 iOS 的文档浏览器、导航栏搜索和分享入口。

演示从一个 iPad Playground 里的绘图原型开始。Tina 把它放进名为 ShapeEdit 的多平台 App,先配置系统能识别的文档类型,再让文档模型实现 FileDocument,最后把默认 TextEditor 换成画布。到结尾时,这个绘图 App 已经可以保存绘图并稍后重新打开。

详细内容

DocumentGroup 把文档管理接入 Scene

02:00)SwiftUI application 由 App、Scene 和 View 组合而成。添加文档支持时,新的 Scene 类型是 DocumentGroup。它可以单独作为 App 的一个 Scene,也可以和多个 DocumentGroupWindowGroup 组合在同一个 App 里。

02:12)官方 Code tab 里的最小例子是一个文本编辑器:

@main
struct TextEdit: App {
    var body: some Scene {
        DocumentGroup(newDocument: TextDocument()) { file in
            TextEditor(text: file.$document.text)
        }
    }
}

关键点:

  • @main 标出 App 入口,body 返回一个 Scene。
  • DocumentGroup(newDocument: TextDocument()) 声明新建文档时使用的基础文档。
  • fileDocumentGroup 闭包传入的文件上下文。
  • file.$document.text 提供可读写绑定,TextEditor 直接编辑文档里的文本。
  • transcript 在 06:49 说明,这个绑定让 SwiftUI 知道文本何时更新,并负责注册 undo 和标记文档 dirty state。

文档类型先让系统认识文件

04:43)Xcode 的文档型 App 模板会在 Info.plist 里加入 Document Type。里面的 identifier 是 Uniform Type Identifier(统一类型标识符),系统用它把磁盘上的文件和 App 关联起来。

05:07)plain text 这类类型由别处声明,App 只需要导入。ShapeEdit 要定义自己的绘图格式,所以要填写 Exported Type Identifier:给格式写描述,让它符合 public data 和 public content,再分配文件扩展名。

08:24)transcript 特别区分了 importedAsexportedAs。导入类型是 computed variable,因为它的值可能随着已安装 App 变化;导出类型由当前 App 拥有,可以声明为常量。

FileDocument 定义磁盘上的文档

07:41)ShapeEdit 的文档类型是一个 value type,并遵守 FileDocument 协议。这个协议代表磁盘上的文档,第一步是定义 readableContentTypes,它是一个 UTType 数组。SwiftUI 用这个数组判断用户要打开的文件类型是否被 App 支持。

09:11)读取文档时,初始化方法会拿到 FileWrappercontentType。演示里删除模板代码后使用 JSONDecoder,因此文档类型需要遵守 Codable

09:43)写出文档时,方法同样接收目标 FileWrappercontentTypeFileWrapper 是 inout 参数,可以创建新的 wrapper,也可以更新已有 wrapper。演示里使用 JSONEncoder 把文档写回文件。

10:12)文档模型是 struct,因此保留值类型的 copy-on-write 行为。transcript 给出的实际收益是:用户还在画图时,App 已经可以开始保存。

从文本模板换成绘图文档

10:32)文档支持准备好后,演示把 Playground 里的画布代码加进项目。项目里有一个 graphic type 用来描述图形属性,也有一个 Canvas View 用来显示图形。

10:56)接着把文档的数据类型从 text 改成 graphic type,加入初始图形,再把 TextEditor 替换成 canvas。最后运行的 ShapeEdit 可以保存绘图,也可以之后再打开同一份绘图。

核心启发

  • 文本文档编辑器

    做什么:做一个打开原始文本文件的轻量编辑器。 为什么值得做DocumentGroup 已经处理打开和管理文档,TextEditor 可以直接绑定到 file.$document.text怎么开始:先用官方 snippet 跑通 DocumentGroup(newDocument:),再让文档类型遵守 FileDocument,把可读类型放进 readableContentTypes

  • 绘图或白板 App

    做什么:把 iPad Playground 里的绘图原型迁移成可保存文件的多平台 App。 为什么值得做:session 的 ShapeEdit 演示正是从画布原型开始,最后保存并重新打开绘图。 怎么开始:先定义 graphic type 和 Canvas View,再把文档数据从 text 换成 graphic type,读写层用 JSONDecoderJSONEncoder 处理。

  • 日志或崩溃报告查看器

    做什么:给一个普通工具 App 增加打开 EML、crash report 或日志文件的入口。 为什么值得做:transcript 明确提到 Mail 和 Console 主要界面并非文档型,但仍支持打开额外文件。 怎么开始:保留原来的 WindowGroup,再组合一个 DocumentGroup 专门处理文件打开场景。

  • 自定义格式的项目文件

    做什么:为设计稿、工程配置或创作项目定义自己的文件格式。 为什么值得做:系统需要 UTI 才能把磁盘文件和 App 关联起来,ShapeEdit 通过 Exported Type Identifier 声明自己的格式。 怎么开始:在 Info.plist 的 Document Type 和 Exported Type Identifier 中使用同一个类型标识,让该类型符合 public data 和 public content,并分配扩展名。

关联 Session

评论

GitHub Issues · utterances