大多数浏览器和
Developer App 均支持流媒体播放。
-
SwiftUI API 的设计:渐进式呈现
探索渐进式呈现 (SwiftUI 的核心原则之一),并了解它会如何影响我们的 API 设计。我们将介绍如何使用渐进式呈现,讨论它对于快速迭代和探索的支持,并帮助您在代码中充分利用它的优势。
资源
-
下载
Sam Lazarus: 大家好 我叫 Sam 是 SwiftUI 团队的一名工程师 在设计 SwiftUI 时 我们一直致力于 依照明确定义的原则 和渐进式呈现做决策 今天我们将着重强调其中一点 渐进式呈现 在 SwiftUI 团队中 我们花了很多时间 思考并构建新的 API 但你可能没有意识到 当你在构建一个可复用构件 或抽象时 你也是一名 API 设计师 在这次谈话中 我们想揭开 我们设计过程的帷幔 分享我们对 渐进式呈现的了解 这样 下一次你在构建可复用构件 或抽象时 就可以使用一样新的工具
让我们先来聊一聊 渐进式呈现到底是什么吧 其实这并不是 API 设计 所独有的 事实上 你可以在最常见的 macOS UI 保存对话框中 看到它的使用 当你第一次看见保存对话框时 它已经为你增添了一个默认位置 此外 对话框里还有一个下拉列表 其中包含一些常见的位置以便选择 最后 如果你需要浏览文件系统 以找到正确的路径 可以展开对话框 以显示更复杂但功能更强大的 UI 这包含了不同层次的复杂性 可以在需要时显示 这与我们希望通过 API 提供的体验是相同的 这些代码相当于提供了 良好的UI体验 使你的 API 使用感变好
作为开发者 我们习惯于 从编写代码的角度来查看代码: 也就是代码声明的位置 但为了让代码更好使用 我们必须从一个不同的角度来看 也就是代码实际使用的地方 我们称其为调用端
而渐进式呈现 就是通过 API 的设计 使调用端的复杂性 随着用例的复杂性而增长
一个理想的 API 应当 既简洁 易操作 又足够适应强大的用例
这对开发者很有利 首先 这最大程度上缩减了 首次构建和运行所需的时间 使你能够更快使用 API 这也降低了代码的学习曲线 防止 API 因与所有用例无关的概念 陷入停滞
最后 它创造了一个紧密的反馈循环 通过渐进式呈现的 API 你可以一点一点地添加内容 查看每一步创建的东西
所有这些因素使 App 开发 成为了一个快速优化循环 而不是单一的大规模前期投入
因此渐进式呈现 是一盏有用的指路明灯 但我们该如何设计特定的 API 使它们采用这一原则呢 在 SwiftUI 团队 我们首先考虑了常见用例 为了逐步展开功能 我们需要确定什么是简单用例
我们还致力于提供智能默认值 以便常见用例 能只列出所需的内容 接下来 我们的目标是 优化调用端 确保调用端的每个字符 都有目的 最后 我们要设计 API 确保它们能整合各个部分 而不是枚举可能性 让我们来看看 SwiftUI 上的一些例子 从我们如何考虑常见用例开始 在这方面 SwiftUI 做得特别好的一处是标签
例如 当创建按钮时 我们要求你 为按钮提供一个标签 大多数情况下 这个标签只是文本 用来描述按钮的作用 而 SwiftUI 为你提供了 一个简洁的拼写方法 但如果你想进一步自定义按钮 SwiftUI 也 提供另一个重载函数 即将任意视图作为标签
这使你能通过这个简单的控件 构建复杂的功能 但是由于这个 API 仔细 考虑了它的常见用例 99% 的情况下 你只需要简单版本
这个标签模式 在 SwiftUI 中随处可见 我说随处可见是认真的 我们在整个框架中 都考虑了常见用例 接下来 我们看看提供智能默认值 为了简化常见用例 我们要为所有 没有明确指定的东西 提供智能默认值 这里最好的例子就是 SwiftUI 中最常用的 API 之一 Text Text 是智能默认值一个很好的例子 你可能已经编写了 数百次这样的代码 在编写时你没有考虑 所有不必指定的内容
有了这段代码 SwiftUI 将通过在应用包中 查找带有环境语言的本地化字符串 来本地化文本 它会自动适应当前的配色方案 直接支持深色模式 它会根据当前的 辅助功能动态类型大小 自动缩放文本 我们之前也提到过这些行为 但文本在幕后做了更多的事情
例如 两个文本并排放入栈中 文本之间的距离将按当前上下文 自动调整为正确行间距 所有这些行为都可以人为指定 但 SwiftUI 的智能默认设置 意味着当它们与你的用例无关时 它们不会出现在调用端
Text 是这样一个例子 它的最简案例是极其简化的 但智能默认值适用于 所有类型的调用端 以工具栏为例 这是一个工具栏 上面有一堆按钮 如果不明确指定它们的位置 工具栏按钮会根据平台惯例放置 在 macOS 上 它们会出现在工具栏的前缘 但在 iOS 上 它们会出现在 导航栏的后缘 最后 在 watchOS 上 只出现第一个项目 固定在导航栏下面 这种方法在大多数情况下都很有效 但如果你确实需要更多的控件 我们也提供了额外的 API 明确指定 项目的放置位置 同样 如果需要 你可以进行自定义 但是智能默认值可以处理大多数情况
参照常见用例并提供智能默认值 可以创造一些非常棒的体验 但如果这些 API 使用感较差 或不完善 它可能会破坏整体效果 这就引出了我们的最后一条策略 优化调用端 说到这一点 我们看看 另一个 API 即 Table
多列表格是功能非常丰富的控件 需要配置的东西很多 功能也很多 但是大多数表格要简单得多 也不需要所有这些功能 我们希望表格能够处理 这种更复杂的行为 而它最详细的格式 已经做到了这点 它支持排序 包含丰富单元格内容的 多列 分段行等等
但我们也希望在更常见的情况下 提供出色的体验感 所以 我们看看这个简单表格的 完整代码 并看看我们 如何优化它的调用端 首先 我们拆分一下这个例子 表格首先指定 如何为每一行生成数据
在这里 我将枚举当前正在读的 每一本书 并为每一本书创建一个表行 接下来 它会指定如何 用每一行的数据填充列 在这里 我创建了 一个标题列和一个作者列
它还会用绑定来排序次序 以允许用户单击 表列 header 时更改排序
最后 我添加了一些代码 以在排序次序发生变化时 重新对表数据排序 这有很多的信息 我们看看 该如何优化这个调用端 以真正实现渐进式呈现
最常见的一个用例与行有关 大多数时候 行字段就像这个例子中一样 一个集合上有一个 ForEach 为每项提供一个表行
开发人员不需要 自己循环所有这些内容 SwiftUI 提供了一个方便的功能 在底层处理这些内容 通过将集合直接传递到表格 可以在幕后完成 ForEach 行为 极大地简化了我们的调用端 但这还可以进一步简化 其它常见用例有什么呢 大多数时候 当我想在表格中显示的一个值 是字符串时 我会直接使用文本在列中显示它 这种情况下 我们也会优化调用端
只要值路径指向一个字符串 我们就允许省略 与 TableColumn 关联的视图
这是另一个重要的简化 但仍有更多需要优化的部分 调用端中有一些信息 并不是所有表格 都需要关注的 那就是排序次序 表格最简单的用例和排序完全无关 因此 我们也提供了一个 本身不涉及 排序的表格版本 这就引出了我们的最后一次迭代 简洁多了 这个 call site 的每个字符 都有一个明确的目的 为了做到这点 我们每一步 都问了自己两个关键问题 “我们应该为哪些 最常见的用例提供便利” 以及“什么信息总是必要的” 这些指导性的问题 可以很好地帮助你 优化 call sites 但要谨慎应用 如果不仔细考虑它们 对 API 的影响 可能会把你引入歧途 这就引出了我们的最终策略 组合 而非枚举 进一步说明这一点 我们来谈谈 SwiftUI 布局系统一个部分的设计:Stack 特别是 HStack 首先 我们想想 HStack 的 关键信息是什么 它需要知道栈中应该有什么内容 以及这些内容 应该如何在栈中排列 我们已经有视图生成器 来指定 HStack 的内容 所以我们要专注于排列 回到我们强调的指导性问题 在 HStack 中排列元素时 最常见的用例是什么 我有时想显示一个像这样的栈 它会从前端开始 一个接一个地显示盒子
另一种常见的情况是希望元素居中 最后 我可能想把元素 向尾部对齐
VStack 已经有了一个 与此类似的 API 即对齐 所以创建一个类似的 enum 来排列栈中的元素似乎很吸引人 这支持我们提到的所有使用情况 通过指定 HStack 的排列 我可以选择前对齐 尾对齐 或居中排列 这取决于我想要什么 但如果我现在想均匀地间隔元素 或者只在元素之间 或最后一个元素之前设置间隔呢 这开始变得格外混乱 但更重要的是 这是不可持续的 我必须为我们想要的每个行为 添加一个 enum 案例 而我们可能无法把 所有有用的案例考虑周全 当你发现自己在列举常见情况 而不是为它们提供方便时 试着把你的 API 分解成 可以构建解决方案的 可组合块 组合 而非枚举
在栈的例子中 SwiftUI 提供了 Spacer 让你将它与栈的元素组合 以构建我们列举的所有间距方案 诸如此类 这也是今天这一 API 的起源
为渐进式呈现设计最佳体验 不仅仅在于最小化调用端 还在于仔细思考 该如何缩放调用端 以处理所有用例 在这种情况下是通过组合
当你编写代码时 同样仔细考虑自己创建的组件 可能会非常有帮助 回顾一下 首先是考虑常见用例 通过应用渐进式呈现 你编写的代码 会在最常见的用例中 节省你的时间 智能默认值将意味着 你不必考虑这些 常见用例的细节 努力优化你构建的调用端 会允许你快速迭代 最后 利用组合 可以让你构建足够灵活的 API 以适应它们的所有用例
因为你是一名 API 设计师 你可以将这些经验 应用到你每天编写的代码中 无论它是为别人设计的 还是只是自己使用的 谢谢观看
-
-
1:59 - Declaration Site Example
struct BookView: View { let pageNumber: Int let book: Book init(book: Book, pageNumber: Int) { self.book = book self.pageNumber = pageNumber } var body: some View { ... } }
-
2:13 - Call Site Example
VStack { BookView(book: favoriteBook, page: 1) BookView(book: savedBook, page: 234) }
-
4:18 - Button Label
Button("Next Page") { currentPage += 1 }
-
4:36 - Button label expanded
Button { currentPage += 1 } label: { Text("Next Page") }
-
4:43 - Button label advanced case
Button { currentPage += 1 } label: { HStack { Text("Next Page") NextPagePreview() } }
-
4:56 - Button label common case
Button("Next Page") { currentPage += 1 }
-
5:30 - Text example
Text("Hello WWDC22!")
-
6:12 - Stacks of Text
VStack { Text("Hello WWDC22!") Text("Call to Code.") }
-
6:46 - Toolbar
.toolbar { Button { addItem() } label: { Label("Add", systemImage: "plus") } Button { sort() } label: { Label("Sort", systemImage: "arrow.up.arrow.down") } Button { openShareSheet() }: label: { Label("Share", systemImage: "square.and.arrow.up") } }
-
7:20 - Toolbar with explicit placement
.toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { Button { addItem() } label: { Label("Add", systemImage: "plus") } Button { sort() } label: { Label("Sort", systemImage: "arrow.up.arrow.down") } Button { openShareSheet() }: label: { Label("Share", systemImage: "square.and.arrow.up") } } }
-
8:09 - Advanced use case table
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(sortOrder: $sortOrder) { TableColumn("Title", value: \Book.title) { book in Text(book.title).bold() } TableColumn("Author", value: \Book.author) { book in Text(book.author).italic() } } rows: { Section("Favorites") { ForEach(favorites) { book in TableRow(book) } } Section("Currently Reading") { ForEach(currentlyReading) { book in TableRow(book) } } } .onChange(of: sortOrder) { newValue in favorites.sort(using: newValue) currentlyReading.sort(using: newValue) } }
-
8:41 - Simpler table use case
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(sortOrder: $sortOrder) { TableColumn("Title", value: \Book.title) { book in Text(book.title) } TableColumn("Author", value: \Book.author) { book in Text(book.author) } } rows: { ForEach(currentlyReading) { book in TableRow(book) } } .onChange(of: sortOrder) { newValue in currentlyReading.sort(using: newValue) } }
-
9:58 - Table collection convenience
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(currentlyReading, sortOrder: $sortOrder) { TableColumn("Title", value: \.title) { book in Text(book.title) } TableColumn("Author", value: \.author) { book in Text(book.author) } } .onChange(of: sortOrder) { newValue in currentlyReading.sort(using: newValue) } }
-
10:23 - Table string key path convenience
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(currentlyReading, sortOrder: $sortOrder) { TableColumn("Title", value: \.title) TableColumn("Author", value: \.author) } .onChange(of: sortOrder) { newValue in currentlyReading.sort(using: newValue) } }
-
10:51 - Table without sorting
var body: some View { Table(currentlyReading) { TableColumn("Title", value: \.title) TableColumn("Author", value: \.author) } }
-
13:37 - Stack example: leading
struct StackExample: View { var body: some View { HStack { // leading Box().tint(.red) Box().tint(.green) Box().tint(.blue) } } }
-
13:40 - Stack example: centered
struct StackExample: View { var body: some View { HStack { // centered Spacer() Box().tint(.red) Box().tint(.green) Box().tint(.blue) Spacer() } } }
-
13:42 - Stack example: evenly spaced
struct StackExample: View { var body: some View { HStack { // evenly spaced Spacer() Box().tint(.red) Spacer() Box().tint(.green) Spacer() Box().tint(.blue) Spacer() } } }
-
13:43 - Stack example: space only between elements
struct StackExample: View { var body: some View { HStack { // space only between elements Box().tint(.red) Spacer() Box().tint(.green) Spacer() Box().tint(.blue) } } }
-
13:46 - Stack example: space only before last element
struct StackExample: View { var body: some View { HStack { // space only before last element Box().tint(.red) Box().tint(.green) Spacer() Box().tint(.blue) } } }
-
13:47 - Stack example: space only after first element
struct StackExample: View { var body: some View { HStack { // space only after first element Box().tint(.red) Spacer() Box().tint(.green) Box().tint(.blue) } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。