大多数浏览器和
Developer App 均支持流媒体播放。
-
SwiftUI 中的叠放、网格和大纲
改进后的叠放、新列表和大纲视图可更快,更有效地在 SwiftUI app 中显示详细数据。 网格工具现已首次在 iOS 和 iPadOS 上使用,它是一种新的多平台工具,用于表示与叠放和列表一起使用的分层数据。了解如何使用 SwiftUI 最新改进的工具在使用表格视图时让屏幕显示更多内容,创建平滑滚动和响应式叠放以及为 vStack 不能提供的内容构建列表视图。 使用新的网格视图以及公开组,可以进一步扩展布局选项。 要想充分利用本节内容,我们建议你先查看“ SwiftUI App 要点”,其概述了 2020 年 SwiftUI 的所有新功能。如果你还不太熟悉 SwiftUI 编码,我们还建议你观看 2019 年的 “ SwiftUI 要点”演讲。
资源
相关视频
WWDC23
WWDC22
WWDC20
WWDC19
-
下载
(你好 WWDC 2020) 你好 欢迎参加 WWDC
你好 欢迎观看 《SwiftUI 中的叠放、网格和大纲》 我叫 Cody 是一名 SwiftUI 开发工程师 稍后 我的同事 Curt 将与我一同讲解本次讲座的内容 SwiftUI 提供了各种各样的内置布局图元 用以按照水平和垂直顺序排列视图集合 这些图元可以单独使用 以解决基本的布局需求 也可以组合使用 以构建带有自定义行为的复杂视图 macOS 下的新版 Notification Center 即采用 SwiftUI 实现 是将组合式流程 应用到工作中的优秀案例 将简约叠放与网格组合起来 利用层级结构、对齐和间距功能 来整理众多信息 这样呈现出来的结果既美观又好用 在开发你自己的 app 时 我鼓励你从类似的角度进行思考 我们在设计 SwiftUI 的布局图元时 便时刻在考虑它们之间的组合问题 通常来讲 一次简单的输入 并不能实现你想要的全部效果 那么你接下来就要把它 与另一个能补充其行为的 简单类型结合起来 在本场讲座中 我将会为大家讲解 SwiftUI 的布局图元家族的一些新成员 我首先要回顾一下两个最基本的类型 也就是水平叠放和垂直叠放 我还要介绍一对新的类型 用来创建能够延迟增长的网格布局
之后 我会讲解现有列表类型的新特性 我们能够用它来展示层级数据
最后 Curt 会为大家深入讲解 大纲和表格 并展示一些能够用来渐进式显示 用户界面控件的技术 我先从叠放开始讲起 叠放是 SwiftUI 中最简单的布局图元 不过在讨论叠放之前 我得先讲一讲三明治的问题 如果你看过 《SwiftUI 简介》那场讲座的话 你应该知道 我的朋友 Jacob 一直在努力研发 一款用来制作三明治的 app 我本人在三明治鉴赏这方面也算略有心得 因此我觉得把这两件事放在一起讲 会比较有意思 这是 Jacob app 的画廊视图 展示了一些让人格外难以忘怀的午餐照片 我这里所要使用的数据模型非常简约 只包含一个 ID 一个名称 一个星级评价 以及一张画廊用的首屏大图
在画廊里显示个别三明治 所用的视图也同样简约 它会显示一张大小可调节的首屏大图
还会在上面添加 包含三明治相关信息的叠加层
每张首页大图上方叠加的横幅视图 都会使用一个 VStack 来排列三明治的标题和星级评价标志
所谓的星级评价标志其实只是 水平叠放在一起的几张图片
我的初步实现非常直截了当 我要使用一系列垂直叠放的三明治视图 来展示我的画廊 随着我拍摄照片数量的增加 我的三明治列表也会急剧扩展 所以我需要加入 ForEach 视图 将所有三明治罗列出来 再为它们生成单独视图
除此之外 叠放并不会自己滚动 所以我需要使用 ScrollView 收罗所有数据 目前来讲 我很满意 但当我准备加载自己的海量三明治照片时 我开始注意到一个问题 我的画廊需要显示的照片越多 展示时屏幕响应所需的时间也就越长 我需要的是一种 能够逐步自我构建的延迟叠放 一开始只需要渲染第一屏图像 余下的部分可以在用户滚动画廊时 按需要加载 我们要为大家介绍 两种全新的 SwiftUI 叠放类型 帮助直接解决这个问题 那就是 LazyVStack 和 LazyHStack 延迟叠放 与 VStack 和 HStack 十分相似 只是它们会随着图像的出现 逐步渲染叠放中的内容 这完全符合我的需求 它的视图并没有妨碍主线程 对每一张图像进行加载和测量 整个 app 的内存足迹 也不会发生无必要的增长 我只需要用 LazyVStack 来替换我的 VStack
这样我的画廊便可以逐步加载了
在这里 我还想指出一点 如果你还记得“评价视图”的定义的话 用来对 HeroView 画廊 进行定义的垂直叠放 并不是这里屏幕上 所显示的唯一一个叠放
每个 HeroView 都有自己的水平叠放 用来编排星级评价标志 以及一个 ZStack 用来把评价放在首页大图之上 那么值得一问的是 既然我已经让我的外层叠放延迟了 这些叠放是否也应该延迟呢?
在这个案例里 答案是否定的 虽然我想让垂直叠放延迟 特别是因为它会滚动 但我并不想让它花时间 渲染屏幕前面的一切 毕竟在不滚动的情况下 大部分内容都是看不到的
另一方面 让指定的 HeroView 内的叠放延迟 并不一定能带来任何好处 当视图出现在屏幕上的那一刻 所有内容便会立即可见 这样就必须同时加载所有内容 无论容器的默认行为是什么 通常来讲 如果你不确定要使用哪种类型的叠放 就使用 HStack 的 VStack 通过延迟叠放来解决 对 Instruments 进行程序概要分析之后 发现的性能瓶颈问题 现在我想介绍一个新的类型集合 那就是延迟网格 让我们回到我的三明治画廊
我很喜欢它在 iPhone 上的呈现效果 但如何把它放到更大的屏幕上呢? 我们把它移动到 iPad 上看看
还是一样 只是变大了 这并不是我想要的效果 既然屏幕上多出来这么多空间 我真正想要的是 让屏幕上能显示出更多的三明治
如果我能把它从一列图片 变为由多列图片组成的网格 那么就可以大大提升三明治的整体密度 这听起来像是 向 SwiftUI 的布局图元家族 新增两名成员所要做的工作 它们的名字分别是 LazyVGrid 和 LazyHGrid (LAZYVGRID 和 LAZYHGRID) 顾名思义 它们可以构建内容网格 用途与叠放相似
使用 LazyVGrid 我可以轻松实现一个多栏布局 从而提高我视图中的三明治密度 我们来看一看它的工作原理
这个延迟叠放与我们之前看到过的相同 只是为了配合 iPad 的屏幕而放大了 我要进行更新 让它显示三明治的方式从单栏变为三栏
它与之前范例的主要区别 在于我的布局容器 我要使用 LazyVGrid 而不是 LazyVStack 我要传递数值集合 好让 SwiftUI 知道 如何计算网格中这几栏的宽度 稍后我会深入讲解这个问题 除了关于栏的描述之外 我定义网格的方式 和定义叠放的方式是一样的 都是通过传递视图构建工具 生成网格所包含的各个单独视图
为了描述网格里的各个栏 我要创建一个 GridItem 数值的阵列 其中每一项都详细说明了 如何计算某一栏的宽度 这里 我要定义三个栏 网格项都默认可伸缩 所以这样的排列 会用同等宽度的栏来填充整个网格
横向放置的时候也是如此 栏的数量还是一样的 只不过宽度有所增加而已 网格布局也可以根据现有空间进行调整 从而创建出数量可变的栏 举例来讲 我这里已经声明了一个自适应 GridItem 能够生成尽可能多的同等宽度的栏 同时将这些栏的最小宽度 维持在一个具体数字上 这对于横向模式十分有利 可以拥有放置更多栏的空间
自适应网格项在 macOS 上也非常好用 因为可以任意调整窗口大小 这些新图元的表现力令我十分激动 在让 Curt 接替我进行讲解之前 我要讲的最后一个主题是列表 列表绝不仅仅只是基础的布局图元 它们会相互互动 同时提供对选择管理和滚动的支持 列表内容始终延迟加载 我不知道你们感想如何 但是我个人认为 三明治的话题已经讲得足够多了 我们来看一款由 Curt 开发的 很酷的新 app 名叫 “ShapeEdit” ShapeEdit 是一款基于文档的 app 能够在 macOS、iPadOS 和 iOS 上运行 缩小一下 我们就可以看到 ShapeEdit 内的窗口侧边栏视图 我们要在这里 使用列表来列举 canvas 上的形状
现在 canvas 上已经有了一个图形阵列 我们要用这个图形阵列 在侧边栏里填充一行行的内容 从而形成一个扁平的形状列表 太酷了 我在玩这个 app 的时候得到了很多乐趣 甚至受它启发 添加了一个分组收藏图形的特性 这些组也可以包含其他的组 现在 这个扁平列表需要表现为 由元素组成的任意深度的树 我们已经为列表添加了一个新特性 能够完美地达到这一目的 我对此感到十分激动 下面我就来讲一讲这个特性 要把我的列表变成大纲 只需要告诉列表如何遍历数据树即可 我会用一个新的初始化器 来提供这个图形模型上的子键路径 而 SwiftUI 则会完成剩下的工作 只需要变动这一个地方 即可让侧边栏显示完整的形状层级结构 很了不起 正如你可能想象的一样 为了实现大纲创建的自动化 还有许多有趣的工作正在进行中 接下来将由 Curt 来接替我 为大家展示 如何利用列表所使用的这些工具 在你自己的用户界面中 实现渐进式披露 Curt?
谢谢 Cody 像这样将列表转换成大纲真的非常酷 下面由我来深入讲解其中的工作原理 我认为这些细节非常出色 你可以把里面的部分内容 用在你自己的 app 里 Cody 已经向我们展示过 如何通过将子键路径传递给列表的方式 让 ShapeEdit 在侧边栏里显示图形大纲 我一直在想 要是我们的 app 能够支持多个 canvas 并草绘一个实物模型就好了 在这个实物模型中 每个 canvas 都会占据一个不同的节表 每个节表里也各有一个单独的大纲 让我们来看看 如何实现这样的自定义大纲
正如 Cody 所说 列表是一种高级结构 能够帮我们对选择进行管理 那么我们就保持这一点 然后在列表内 使用一个 ForEach 来 对这些 canvas 进行迭代 对于每一个 canvas 我们都用 Section 来添加一个标头 用来显示这个 canvas 的名称 最后 这个 Section 的内容 就成了 SwiftUI 的一种新视图 大纲组 大纲组与 ForEach 很像 只是它不会对扁平集合进行迭代 而是对树结构数据进行遍历 这里 它需要一个图形阵列和子键路径 大纲组会生成一个大纲 里面每一项都是一个图形行
让我们转向 Xcode 了解实时使用场景 这里是预览中显示的图形大纲 SwiftUI 大纲不仅可以在 macOS 上使用 也可以在 iOS 上使用 在 iPad 和 iPhone 上都能够使用 如此强大的内置大纲功能 这真是太好了 让我们前往实时预览 看看这些分组如何工作
我可以轻点披露标志 展开和折叠这些分组 我们来更新一下这个视图 好让它显示所有的 canvas 首先 我们要在这个列表内 添加一个大纲组 把这个图形行包含进去
然后 把列表中最靠前的两个实际参数 移动到这个大纲组里去
请注意 我们的预览到现在还没有发生过变化 直接放在列表里的大纲组 和使用子参数的列表是一样的 接下来 我们来更改一下视图 让它使用 canvas 而不是图形 通过“点击并选择重复”的命令 把这个大纲组放进 ForEach
之后 用模型里的 canvas 替换这个实际参数
同时 重命名这个参数
最后 我要更改这个大纲组 让它遍历单个 canvas 上的图形 现在我们可以查看所有 canvas 上的图形 但它们都是在一起运行的 我们来添加几个节表标头 我可以按下 Shift+Command+L 打开资料库 然后筛选 让它显示节表 我可以把节表拖动到里面 然后让标头显示 canvas 的名称
请注意 由于我们使用了一个 SidebarListStyle 因此 这里显示的是 iOS 14 所采用的这种美丽的粗体标头 这些标头也可以加以展开和折叠
我觉得这非常酷 通过层级列表和大纲组 SwiftUI 在 mac 和 iOS 上提供了 两款出色的新工具 用来渐进式地显示信息 有时 app 需要隐藏和显示控件 或是其他不采用标准层级结构的信息 比如这个 inspector 气泡弹出框 对于类似这样的情况 我很高兴地为大家介绍第三种新工具: 披露组 披露组可以为披露标志提供 标签和内容 当你的用户轻点或点击披露标志时 内容会显示出来 当他们再次轻点或点击的时候 内容则会隐藏起来 让我们来看看这款工具的使用方法 这是我们的 inspector 我们的控件能够 帮助调整填充、阴影和文本属性 这一切都包含在一个表单中 对于像这样的控件集合来说 这是一个完美的选择 你也可以在 macOS 的新设置画面里 使用表单 让我们快速了解下 这个 inspector 在 app 中的工作效果
ShapeEdit 在 iPad 上运行的效果非常好 我可以选择选择一个形状 然后打开 inspector
更改颜色 添加阴影
甚至更改形状
让我们回到代码上
这个 inspector 的工作效果很不错 就是有点繁忙 我们来看看能否帮它清理一下 首先 我要把所有的填充控件 都放进披露组
从资料库中选择披露组
然后把标题设置为 Fill
注意 现在这些填充控件 都在 inspector 中被折叠到一起 就像大纲一样 我们可以展开和折叠这个披露组
应该给这个披露组加个图标 我们可以用标签来完成 我们只需移除这个便利属性 然后给标签添加一个尾随的闭包即可
这里随便放什么视图都可以 但这个新的标签类型能够便于 通过语义将标题和图标结合到一起
这里我可以使用优质的 SF Symbol 图像 我非常喜欢使用 rectangle.3.offgrid.fill
看起来很不错 让我们对阴影和文本控件 也进行同样的处理
完成之后 inspector 看起来十分不错 只有一点是我想更改的 我认为 用户会经常对填充设置进行调整 因此 我希望他们在打开 inspector 的时候能够看到这些内容 现在 让我们来试试 SwiftUI 中的披露组 可以绑定到一个 能控制展开的 Boolean 属性上 我会添加 Boolean 状态 让它作为真值来源
并将它的值默认设置为真
然后 设置披露组 让它绑定到新状态上
(构建成功)
现在我们的填充控件已经默认为已展开 很好 以上就是使用大纲组和披露组 对 app 中渐进式披露的信息 进行管理的方法 在我总结之前 我们先来看一看大纲组的工作原理 对于 Cody 之前所提到的组合原则来说 它是一个非常好的例子 我们不用理解这些原理 也能使用大纲组和披露组 但我认为它真的很酷 希望你们也能这样想 (观察内部原理) 我们这里有一个大纲组 位于一个图形集合之上 SwiftUI 将大纲组展开 变成一个 ForEach 还是位于同一个图形集合之上 而这个 ForEach 的主体是一个披露组
注意 每个披露组的标签 都是用原集合中的单个元素生成的 而每个披露组的内容则是另一个披露组 只不过是位于那个元素的子级之上 (从这里往下都是披露组!) 展开过程会持续进行 直到我们找到不包含子级的图形为止 但由于 SwiftUI 只会在 有人打开披露组之后 才会对这个披露组的内容进行评估 因此 实际上这个过程被执行的 就只有极少的一部分 正如我之前所讲的 你不用理解这个展开的原理 也能使用大纲组和披露组 但我就是非常喜欢这种递归与组成的组合 它是大纲组得以存在的基础 从实用层面讲 我希望今天我们谈到的 用来展示数据的 SwiftUI 工具 能够对你起到帮助 我们了解了 HStack 和 VStack 是用来控制固定项目集放置位置的 合适工具 在滚动视图中 新的延迟叠放十分适用于 展示内容可能非常多的可变项目组 延迟网格提供了一种方便的新方式 让你可以用网格形式来展示集合 列表是一种强大的工具 它能够为选择、滚动 和延迟加载内容提供支持 除此之外 对于今年新加入的 层级数据显示 它也可以提供支持 将表单用在设置和其他控件列表上 就像我们之前在 inspector 范例中 所看到的那样 最后 新的大纲组和披露组 可以让你针对自己的 app 对渐进式展示信息 进行量身定做式的调整 (后续步骤) 如果你想要进一步了解 在 app 中展示数据的最佳方式 可以前往 developer.apple.com 下载 ShapeEdit 所用的代码 此外 请务必观看 《SwiftUI 中的 app 要点》 进一步了解 如何在 app 中创建设置画面 同时 也可以观看 《SwiftUI 中的数据要点》 详细了解如何将模型与视图相连接 至于三明治方面的问题 可以观看 WW 20 的《SwiftUI 介绍》 感谢观看 祝大家一切都好
-
-
2:08 - Sandwich and HeroView
// Sandwich model and gallery item view struct Sandwich: Identifiable { var id = UUID() var name: String var rating: Int var heroImage: Image { … } } struct HeroView: View { var sandwich: Sandwich var body: some View { sandwich.heroImage .resizable() .aspectRatio(contentMode: .fit) .overlay(BannerView(sandwich: sandwich)) } }
-
2:26 - Sandwich Info Banner
// Banner overlay view for sandwich info struct BannerView: View { var sandwich: Sandwich var body: some View { VStack(alignment: .leading, spacing: 10) { Spacer() TitleView(title: sandwich.name) RatingView(rating: sandwich.rating) } .padding(…) .background(…) } }
-
2:34 - Sandwich Rating View
// Sandwich rating view struct RatingView: View { var rating: Int var body: some View { HStack { ForEach(0..<5) { starIndex in StarImage(isFilled: rating > starIndex) } Spacer() } } }
-
2:39 - Scrollable Stack of HeroViews
// Fetch sandwiches from the sandwich store let sandwiches: [Sandwich] = … ScrollView { VStack(spacing: 0) { ForEach(sandwiches) { sandwich in HeroView(sandwich: sandwich) } } }
-
3:53 - Scrollable Stack of HeroViews
// Fetch sandwiches from the sandwich store let sandwiches: [Sandwich] = … ScrollView { VStack(spacing: 0) { ForEach(sandwiches) { sandwich in HeroView(sandwich: sandwich) } } }
-
3:57 - Scrollable Lazy Stack of HeroViews
// Fetch sandwiches from the sandwich store let sandwiches: [Sandwich] = … ScrollView { LazyVStack(spacing: 0) { ForEach(sandwiches) { sandwich in HeroView(sandwich: sandwich) } } }
-
6:09 - Scrollable Lazy Stack of HeroViews
// Fetch sandwiches from the sandwich store let sandwiches: [Sandwich] = … ScrollView { LazyVStack(spacing: 0) { ForEach(sandwiches) { sandwich in HeroView(sandwich: sandwich) } } }
-
6:18 - Three-Column Grid of Sandwiches
// Fetch sandwiches from the sandwich store let sandwiches: [Sandwich] = … // Define grid columns var columns = [ GridItem(spacing: 0), GridItem(spacing: 0), GridItem(spacing: 0) ] ScrollView { LazyVGrid(columns: columns, spacing: 0) { ForEach(sandwiches) { sandwich in HeroView(sandwich: sandwich) } } }
-
7:13 - Adaptive Grid of Sandwiches
// Fetch sandwiches from the sandwich store let sandwiches: [Sandwich] = … // Define grid columns var columns = [ GridItem(.adaptive(minimum: 300), spacing: 0) ] ScrollView { LazyVGrid(columns: columns, spacing: 0) { ForEach(sandwiches) { sandwich in HeroView(sandwich: sandwich) } } }
-
8:47 - Outline of GraphicRows
struct GraphicsList: View { var graphics: [Graphic] var body: some View { List( graphics, children: \.children ) { graphic in GraphicRow(graphic) } .listStyle(SidebarListStyle()) } }
-
9:52 - Customizing your outlines
// Customizing your outlines List { ForEach(canvases) { canvas in Section(header: Text(canvas.name)) { OutlineGroup(canvas.graphics, children: \.children) { graphic in GraphicRow(graphic) } } } }
-
13:10 - DisclosureGroup
// Progressive display of information Form { DisclosureGroup(isExpanded: $areFillControlsShowing) { Toggle("Fill shape?", isOn: isFilled) ColorRow("Fill color", color: fillColor) } label: { Label("Fill", …) } … }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。