大多数浏览器和
Developer App 均支持流媒体播放。
-
构建更出色的文稿类 App
了解如何运用 iPadOS 中的最新功能来改进文稿类 App。我们将向你展示如何利用 UIDocument 以及现有的桌面类 iPad 和文稿类 API 为你的 App 增添全新功能。了解如何将数据模型转换为 UIDocument 以及如何使用 UIDocumentViewController 呈现文稿,学习如何将 App 迁移到最新的 API 并探索绝佳实践。
章节
- 2:10 - Creating a document
- 5:46 - Presenting a document
- 9:38 - Migrating your app
资源
相关视频
WWDC23
WWDC22
WWDC20
Tech Talks
-
下载
♪ ♪
Michael:大家好 欢迎观看本次讲座 我是 Michael Ochs 在本次视频中我将谈谈 如何编译更为出色的 以文档为中心的 App 以文档为中心的 App 是 生产力工具中重要的组成部分 尤其对于 iPad 而言 以文档为中心的 App 共分为 3 类: 允许浏览文档的 例如文件 App; 允许查看内容的 例如快速查看; 还有同时允许编辑或创建内容的 例如 Pages 文稿、Keynote 讲演 以及 Numbers 表格 本视频将重点介绍查看 App 和 编辑 App 的改进 并且其中部分内容 同时也适用于浏览 App iPadOS 17 引入了新的视图控制器 这能自动启用 App 的大量功能 并且与 iPadOS 16 所引入的 桌面级 iPad API 及现有的以文档为中心的 API 完美契合 这款新的视图控制器 以模块化的方式进行编译 因此你既能利用实用系统默认设置 同时也能自定义任何个人操作
想要了解更多有关桌面级 iPad API 的信息 欢迎观看 WWDC22 中 “认识桌面级 iPad” 以及“编译桌面级 iPad App”
在 SwiftUI 开发中 DocumentGroup 当前无需任何额外的代码 便可支持视图控制器中所有的功能 想进一步了解 有关 SwiftUI 的信息 欢迎观看 WWDC20 中 “在 SwiftUI 中构建基于文档的 App” 以及 WWDC22 中“在 iPad 上使用 SwiftUI:添加工具栏、标题等” 而在 UIKit 中 这些功能则是可选择的 UIDocumentViewController 是内容视图控制器的新基类 它可与 UIDocument 相结合 一同自动配置导航栏 这就实现了共享、拖放文档、 支持撤消和重做等功能 该新基类也支持自动重命名功能 在本次视频中 你将了解 如何使用 UIDocument 以及如何使用 UIDocumentViewController 呈现文档 接着 我会介绍哪些功能是内置的 以及如何进一步自定义这些功能 最后 我会与你深入探讨 如何利用 UIDocument 对现有 App 进行迁移 首先 我们从创建文档开始
对于基于文档的 App 而言 核心均为 UIDocument UIDocument 是一个抽象的基类 可用于为 App 所支持的 每种文件类型创建子类 所有 UIDocument 均可由 URL 支持 虽然大部分情况下 使用的都是磁盘上的文件 但你也可以使用数据库和 自定义 URL 方案 来保存和加载文档 UIDocument 的加载和 保存操作是异步的 所以如果有必要 这可以实现较长时间的读写操作 因此 UIDocument 是线程安全的 并且可以通过锁和队列协调访问
在实现 UIDocument 子类时 你需要注意这两点: 加载和保存文档 以及提供对文档内容的访问权限 所有文档的加载和保存都非常相似 而内容的访问则会 更具体地取决于文档类型 以及文档在 App 中的使用方式 例如 Markdown 编辑器 所使用的文档类型 可能只支持一种文本属性 或者也可以公开一个更为复杂的接口 以供更新文档中各个单独的部分 在我们深入了解内容访问前 我们先来谈谈加载和保存 对于简单的基于文件的文档 你有两种简便的重写方法 你可以在打开文档时 调用 “loadFromContents:ofType:” 并获取文件内容 你也可以在保存文档时 调用“contentsForType:” 获取文档当前内容 文档内容可以是常规文件的 Data 对象 也可以是用于其他内容的 FileWrapper 对象 想进一步了解更多有关文件类型 及其工作原理的信息 欢迎观看 “统一类型标识符 -- 再次介绍” 技术讲座 例如在这里 文档处理的是 常规 Markdown 文件 所以我们希望使用一个 Data 对象 现在 如果你想获得全部权限 重写 “saveToURL:forSaveOperation:” 以及“readFromURL:”后 你便可以完整访问 URL 并处理其中所有的读写操作 如果你想将文档存储在数据库中 或对文档的读写操作有特定的要求 该方法也十分有效 需要注意的是 虽然保存操作是异步的 但是在方法返回时 读取操作应该已经完成 以上是加载和保存文档的全部内容 现在 我们需要找到一种 可以访问文档内容的方法 一种访问文档内容的简单方法 就是为该内容添加属性 在这个例子中 我添加了一个包含 完整 Markdown 字符串的 文本属性 正如前面幻灯片中所介绍的 该属性需在初次加载文档时设置 这样 每当用户对文档进行编辑 App 便会更新该文本 为了让 UIDocument 知道何时需要进行保存 每当属性更新时 调用 “updateChangeCount:”即可 调用“updateChangeCount:” UIDocument 可以将文档标记为需要保存 并在适当时候将文本进行自动保存
接下来 我们来谈谈使用新 UIDocumentViewController 呈现文档
与 UIDocument 类似 UIDocumentViewController 也是一个抽象基类 可用于创建子类 该基类负责文档打开、保存和关闭 它用来自相关文档的信息 填充其导航项目 其中包括标题、 导航项目的标题菜单、 UIDocumentProperties 对象以及重命名委托 此外 UIDocumentViewController 还提供了常见操作的快捷键 例如撤销和重做 接着 我们来看看如何实现 UIDocumentViewController 的子类
你的子类共有两种方法进行重写 第一种方法 你可以在打开 与视图控制器关联的文档时 或将已打开的文档分配到 新的视图控制器时 调用“documentDidOpen” 在该方法中填充视图控制器的视图 即可展示文档中的内容 需要注意的是 在调用“documentDidOpen”和 加载视图控制器的视图之间 并没有确定的时间顺序 如果你想要编写稳健的代码 其中一个不错的方式就是 将视图配置移到其自己的方法中 并从“documentDidOpen”和 “viewDidLoad”中进行调用 并且 在配置视图前 你需要检查视图是否已加载 以及文档是否已打开
第二种方法就是重写 “navigationItemDidUpdate” 每当 UIDocumentViewController 需要对导航项目进行更改时 便会调用该方法 你可以在其中添加自定义导航项目 随后 “UIDocumentViewController” 便会尽力减少对导航项目的更改 以尽可能保持你的更改 此外 UIDocumentViewController 还提供“undoRedoItemGroup” 如果你想要显示撤销和重做按钮 将其放入导航栏中即可 并且你需要确保文档 已获得分配给它的撤销管理器 UIDocumentViewController 将会根据是否存在可用的撤销管理器 来改变该组中“hidden”的属性 在必要时启用或禁用该组中的按钮
UIDocumentViewController 可以自动打开和关闭文档 但如果你需要从 视图控制器外部访问文档 调用“openDocumentWithCompletionHandler” 便可实现 UIDocumentViewController 在必要时会进行回调 例如调用“documentDidOpen” 并且在准备好后 调用已完成的处理程序
最后 UIDocumentViewController 还可以提供文档属性 并且 该属性始终指向 与视图控制器相关联的文档 虽然你可以在初始化时提供文档 但这一步完全是可选的 当不存在 与视图控制器相关联的文档 该属性便会自动显示为空状态 想要进一步了解 有关配置空状态的信息 欢迎观看“UIKit 中的新功能” 此外 UIDocumentViewController 可以作为 App 的根视图控制器 假如层次结构中 没有浏览器视图控制器 UIDocumentViewController 便会在导航栏中添加一个文档按钮 用于打开文档选择器 这就需要在 App 的 info.plist 中声明 与相关文件类型对应的 “UIDocumentClass”键 并将其设置为与该文件类型匹配的 UIDocument 子类
在 iPadOS 17 中 UIDocument 遵循 “UINavigationItemRenameDelegate” 并且其会在用户从标题菜单中 调用重命名时 自行处理底层文件更改 如果你使用的是 UIDocumentViewController 它便会自动为你配置重命名 或者你也可以手动将文档设置为 导航项目的重命名委托
以上是你在 iPadOS 17 创建以文档为中心的出彩 App 所需要的部分 接下来 我们来谈谈 如何对现有的 App 进行迁移
迁移你的 App 以充分利用 UIDocumentViewController 这个过程十分简单 只需要三步 第一步 更新内容视图控制器的基类 第二步 将现有代码移到新的回调中 第三 删除不再需要的代码 我们来看看如何对 桌面级 iPad App 视频中 使用的 Markdown 编辑器 示例进行转换 如果你对此并不熟悉 不用担心 接下来 我首先会为你介绍 现有代码中的相关部分
在这里 我们有了 对顶部视图控制器的定义、 由该控制器定义的 document 属性 以及设置初始文档的 init 方法 并且我们在文档中添加了一个回调
首先 我们将基类改为 UIDocumentViewController
现在 该类便会继承自 UIDocumentViewController 但我们会收到一个编译器错误提醒 因为属性“document” 已在超类中存在且具有不同的类型 我们将该属性的名称改得更具体些 比如“markdownDocument” 然后 我们将其设置为计算属性 并将通用的文本属性 更改为该视图控制器中 使用的特定文本类 该代码中最后需要注意的一点是 初始化定式 并且其中我们需要使用的 最后一部分是为文档分配回调 由于文档在视图控制器的 生命周期中会发生更改 因此我们将此操作 移动到每次文档更改时执行
一种简单的方法就是重写文本属性 并添加 didSet 回调 很好 现在基类已经更新到最新了 接着我们要做的就是处理新的回调 在“viewDidLoad”中 我们在导航栏中添加按钮并进行配置 从而在工具栏中实现了自定义 对于 UIDocumentViewController 我们会将其移到新的回调 “navigationItemDidUpdate”中
接着 我们的类已经有了一个方法 “didOpenDocument” 并且该方法几乎和 UIDocumentViewController 中的完全相同 因此 我们只需要对该方法重命名 并调整当前文档可选择的事实
好了 接下来是大家最喜欢的部分: 删除代码 编辑器视图控器遵循 'UINavigationItemRenameDelegate' 但我们不再需要这个协议了 因为 UIDocument 会自动为我们处理所有的重命名 所以 我们删除委托定义、 委托方法的所有代码 以及“renameDelegate”分配
接着 我们还可以删除更多 navigationItem 自定义配置 由于“style” 及“backAction” 均由文档视图控制器自动配置 所以我们可以将其完全删除
此外 还有一个用于创建 UIDocumentProperties 的 “updateDocumentProperties”方法 并且该方法要从多个地方进行调用 但在这里 我们也不需要此方法了 UIDocumentViewController 会为我们完成所有的操作 所以我们删除该方法及其调用站点 以上便是我们需要完成的内容 现在 编辑器视图控制器就只能 处理 App 特定的功能了 它无需处理文档管理的基本任务 以及导航栏的默认配置 你可以专注于 App 中 独特且关键的部分 以上便是你需要了解的全部内容 据此内容 你可以将以文档为中心的 App 提升到全新水平 并为用户提供出色体验
对你的数据模型进行转换 以利用 UIDocument 接着 对内容视图控制器进行转换 以利用新的 UIDocumentViewController 基类 最后 查看视图控制器 并移除不需要的代码 感谢你的观看 ♪ ♪
-
-
3:54 - Loading a document
override func load(fromContents contents: Any, ofType typeName: String?) throws { // Load your document from contents guard let data = contents as? Data, let text = String(data: data, encoding: .utf8) else { throw DocumentError.readError } self.text = text }
-
4:08 - Saving a document
override func contents(forType typeName: String) throws -> Any { // Encode your document with an instance of NSData or NSFileWrapper guard let data = self.text?.data(using: .utf8) else { throw DocumentError.writeError } return data }
-
4:34 - Manually saving and loading a document
override func save(to url: URL, for saveOperation: UIDocument.SaveOperation, completionHandler: ((Bool) -> Void)? = nil) { self.performAsynchronousFileAccess { // Set up file coordination and write file to URL } } override func read(from url: URL) throws { // Set up file coordination and read file from URL }
-
5:08 - Defining document that require saving
class Document: UIDocument { var text: String? { didSet { if oldValue != nil && oldValue != text { self.updateChangeCount(.done) } } } }
-
6:30 - Updating the view hierarchy for a document
override func documentDidOpen() { configureViewForCurrentDocument() } override func viewDidLoad() { super.viewDidLoad() configureViewForCurrentDocument() } func configureViewForCurrentDocument() { guard let document = markdownDocument, !document.documentState.contains(.closed) && isViewLoaded else { return } // Configure views for document }
-
7:17 - Updating navigation items for a document
override func navigationItemDidUpdate() { // Customize navigation item }
-
8:01 - Manually opening a document
documentController.openDocument { success in if success { self.present(documentController, animated: true) } }
-
9:20 - Renaming a UIDocument without UIDocumentViewController
navigationItem.renameDelegate = document
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。