大多数浏览器和
Developer App 均支持流媒体播放。
-
UICollectionView 中的列表
了解如何使用 UICollectionView 在 app 中构建列表和边栏。 充分利用组合布局的灵活性,替换列表视图外观。 了解模块化布局选项,以及它们如何为你的 app 解锁更多设计选项。了解如何在单个 UICollectionView 内将类似于列表图的列表与自定义组合布局结合在一起。 了解如何使用列表,创建更丰富的单元格以及自定义布局,从而在 app 内创建设计合理的信息显示方法。 想充分利用本节内容,建议先基本了解组合布局。 获取更多信息,请从 WWDC19 观看“网格视图布局的新功能”。
资源
相关视频
WWDC20
WWDC19
-
下载
(你好 WWDC 2020)
你好 欢迎来到 WWDC
大家好 欢迎观看 我叫 Michael Ochs 我是 UIKit 团队的一位框架工程师 在这支视频里 我们会讨论 “UICollectionView 中的列表”
(现代集合视图) 你在这里看到的是一个架构 是我们所认为的 现代集合视图设置的一部分 这张图 在《集合视图的改进》中有详细介绍 这张图的不同部分都有一个独立的视频 而在这支视频里 我们会介绍列表配置和列表单元格
不过让我们先说说 集合视图中的列表到底是什么 iOS 14 中的列表使你在集合视图中 可以有类似 UITableView 的外观 我们在 iOS 13 中引入的 组合布局的基础上构建了它 这使它非常灵活而且高度可自定义
我们还极大地改善了 列表的自适应宽高支持 并且在使用 UICollectionView 的列表时 让自适应宽高成为新的默认行为 也就是说 你不再需要费心于 手动计算单元格高度 而只需使用自动布局来构建你的单元格了 集合视图会负责其余的部分 但如果你确实需要手动调整宽高 你仍然可以通过重写单元格子类的 preferredLayoutAttributes FittingAttributes 来实现这个操作
不过现在我们说回自定义化方面 来看看我说的“高度可自定义化”是指什么
你在这里看到的例子是一个 app 顶部一行显示了我最近使用过的表情符号 可以进行正交滚动 在它下面 你看到一个大纲 它把所有表情符号分了组 并且内置了分层结构 然后在底端 我们有一个 看起来非常像 UITableView 的部分 此外 你还可以轻扫每个单元格 来标记你最喜爱的表情符号 你在这里看到的是一个集合视图 结合使用了列表和组合布局 在更深入地了解其中的原理之前 我们先来看列表的组成部分
在 iOS 14 中 我们提供了一种新的类别 叫做 UICollectionLayoutListConfiguration 这是布局端在集合视图中构建列表 所要求的唯一新类别 UICollectionLayoutListConfiguration 是构建在 NSCollectionLayoutSection 和 UICollectionViewCompositionLayout 之上的 两者都是我们在 iOS 13 中引入的 现有组合布局系统中的部分
在这个讲座中 我们不会详细介绍组合布局 所以如果你对它还不熟悉 我强烈推荐你去查看 WWDC 2019 的 《集合视图布局的改进》讲座
(列表配置) 不过现在我们来看看列表配置
列表配置为你提供了已知的 与表格视图中外观相同的样式 即普通、分组以及内嵌组 但我们还会为 UICollectionView 引入它独有的两种新样式 我们称它们为“侧边栏”和“普通侧边栏” 而你可以使用这些新样式 在 iPadOS 14 上构建出色的多栏 app 除了总体外观 列表配置 还使你能够选择显示或隐藏分隔符 以及配置列表的表头和表脚 如果你以前使用过 UITableView 那么所有关于 列表配置的这些术语 你应该都熟悉 不过我们确实另外添加了一些小点缀 这些稍后会说到 但首先 我们来看如何创建一个列表
创建列表最简单的方式 就是创建一个 UICollectionLayoutListConfiguration 为它设置一个外观 然后用这个配置 来创建一个 UICollectionViewCompositionalLayout
在这个例子中 我们用的是内嵌组样式 这会使它看起来 和内嵌组式的 UITableView 完全一样 且布局中的每个分组都相同
所以它和 UITableView 非常相似 我推荐你尽可能地使用这种途径 不过 还有一种 更强大的方式来创建一个列表 我们将它称作“按分组设置”
在“按分组设置”中 你使用完全相同的配置 但不是用来创建一个组合布局 而是用这个配置来创建一个 NSCollectionLayoutSection
之后 在组合布局里 这个代码就可以被用在已有的 分组提供者初始化器中 并在集合视图中为每个分组所调用 以允许你返回 这个特定分组的独立布局定义
你在这里看到的内容 将会生成与我之前展示的 简单设置中完全相同的设计 不过 既然我们现在将它设置好了 就可以根据“按分组” 来自定义我们的布局了
比如 对于这里的第一个分组 我会返回一个自定义网格布局 那是我用现有的组合布局 API 构建的 这非常强大 而且可以用于像是 你在之前一段视频里看到的布局上 当时我在其中展示了 最近使用过的表情符号的正交滚动分组 现在你已经了解了 如何在 UICollectionView 中创建列表 我们来说说 如何在一个列表分组中 配置你的表头和表脚 (表头和表脚) 集合视图中列表的表头和表脚 与你惯用的 UITableView 中的用法不太一样 UICollectionView 中列表的表头和表脚 必须被显式启用 有两种方法来完成
第一种方法 是将你的表头和表脚注册为追加视图 在这个例子中 我们将配置一个表头 不过同样的代码也可以用于表脚 只需将配置中的表头或表脚模式 设置为“追加”
接着 以这种方式配置好后 当需要在屏幕上渲染表头和表脚时 集合视图会要求你提供一个追加视图
提供这个视图的最简单的方法 是通过你的差量数据源上的 supplementaryview 提供器 但你也可以 在你的 UICollectionView 委托上 实行一个等效的方法
在这个回调函数内 你就可以为 elementKindSectionHeader 或 elementKindSectionFooter 检查元素种类 并配置以及返回合适的视图
重要的是要记得在使用这个途径时 你必须在集合视图要求时才提供追加视图 如果在 supplementaryview 回调中返回空值 那么集合视图将断言 所以如果你的布局中 有一些分组要求表头而其它部分不要求时 只需使用我之前展示过的“按分组配置” 并根据这个特定分组是否应该显示表头 来将模式设置成“追加”或“无” 我提到有两种选择 第二种选择只可以用于表头 并且要通过将表头模式设置成 firstItemInSection 方可启用 这会让集合视图 配置这个特定分组的第一个单元格 让它看起来像一个表头
当使用分层数据结构 和新的分组 snapshot API 时 我们推荐使用这个模式
在“差量数据源的改进”中 你可以了解到所有原理内容 但是要记住 使用这个模式时 你的数据源需要注意一点 因为你的数据源中的第一项 不再代表这个分组的实际内容 而是这个分组的表头 它也许只是一个标题 目前我们介绍了集合视图中列表的布局 我们现在来讨论表示的部分 在 iOS 14 中 我们引入了一个 新的 UICollectionView 单元格子类 叫做 UICollectionViewListCell
值得一提的是 只要遵照集合视图的组合性质 你就能够在任何预期的 常规集合视图单元格位置使用列表单元格 还可以将任何 UICollectionView 单元格 与列表分组一起使用 所以你只需选择你需要的零星几个 API 来实现你的目标设计 我们来看看 列表单元格可以为你提供哪些帮助
列表单元格 有更多细粒度支持以配置分隔符嵌套 以及你的单元格内容缩进 相对于 UITableView 轻扫操作现在也是单元格的一项功能了
此外 我们还大大改善了附件 API 当然 你还可以访问默认系统内容 和后台配置 这些你可以在 “现代单元格配置” 中进行全面了解
(分隔符) 那么我们来讨论分隔符
你在这里看到一个单元格的例子 它正在对一个图像 和一个标签进行渲染 下方是一个分隔符 这是一个很常见的布局 但是 你也许已经注意到了 这里看到的布局其实是不正确的 分隔符应该与单元格的首要内容对齐 在这个例子中 不应该是图像视图 而应该是单元格的标签 所以在前端 分隔符应该嵌套 来对齐标签的边缘 (分隔符嵌套) 像这样 (首要内容) 在 UITableView 上 这是通过提供一个基于点的值来实现的 这个值叫做分隔符嵌套 引入这个 API 时非常轻松 因为你也许已经有一种 手动计算 X 轴偏移量的方法了 所以你可以使用同样的方法 也用相同的值来配置分隔符嵌套 不过 在现代的自动布局世界中 你有了安全区域嵌套 所以布局边距 动态字型尺寸和 SF Symbols 变得不再那么简单了 现如今 我们有了高动态环境 所以这些数值随时都会改变 使用了动态字型 和 SF Symbols 甚至连你的图像大小 也会根据用户倾向的字型大小来改变 并因此改变标签的位置 所以很难预先知道标签最后会在什么位置 我们在列表单元格中引入一个新概念 称作分隔符布局指南 这个布局指南和 UIKit 中 现有的布局指南的原理有些差别 它不再将你的内容约束在这个布局指南中 而是你将这个布局指南约束在你的内容中 所以这和你以往惯用的布局指南是相反的 设置分隔符布局指南最简单的方式 就是先配置你的单元格布局 当你的单元格成为了你预期的样子 只需额外添加一个约束即可 将 separatorLayoutGuide 的前导锚点 约束到你的标签的前导锚点 或者单元格中的任何首要内容 列表单元格以及列表分组 接下来会确保自动让分隔符 与你单元格中的首要内容对齐
注意 如果你正在使用 系统提供的内容配置 那么它会自动为你完成这个操作 你也就不用为此担心了 但如果你用的是自定义单元格布局 那么这是一个 确保分隔符正确放置的简单方法
(轻扫操作) 现在我们来讨论轻扫操作
与 UITableView 不同 轻扫操作 现在是列表单元格的一项功能了 你将它们与单元格的内容配置在一起 这样无论你在哪里 配置图像视图或单元格标签 你现在也可以设置 前导或尾随操作的配置了
这会要求单元格与布局之间的交流 所以只有当你的单元格 是在使用“列表配置” 来配置的分组中被渲染时 才支持轻扫操作
如果你要求的是 UITableView 上 轻扫操作 API 的动态性质 也就是只在即将要轻扫时 才创建轻扫配置 你可以重写前导或尾随操作配置访问器 然后在其中创建配置 我们会确保只在用户实际试图轻扫 这个单元格时 才调用访问器
请注意 在你轻扫操作的操作处理程序中 请确保不要捕捉 你正在配置的单元格的索引路径 索引路径不是一个稳定的标识符 每当你在这个单元格上方 插入或删除内容时 这个单元格的索引路径都会随之改变 而这个特定单元格也并不一定会重新加载 所以如果在用户触发了一个轻扫操作时 你使用了存储的索引路径 来获取单元格的数据模型 那么你实际操作的 也许会是另一个单元格内的数据 这个对于删除操作尤为危险 因为你也许会删除错误的数据 取而代之的 是直接捕获数据模型 或者一个稳定的标识符 让你能够使用它来识别这个单元格的内容 差量数据源和它的稳定项标识符 以及 iOS 14 中新的单元格注册类别 都完美适用于此类操作
下面 我们来讨论附件 (附件) 在 UITableView 上 附件 API 十分有限 你可以访问附件类别和附件视图 两者是互斥的 而且只与你的单元格尾随端相关
列表单元格提供许多附件类别 而且使你能够为单元格的尾随 和前导端都配置附件 你甚至可以在同一端配置多个附件
此外 在 UITableView 单元格中 附件更像是装饰视图 而在列表单元格中 它们能够启用功能性
比如 如果你用 重新排序附件配置一个单元格 我们会在用户轻点这个附件时 假定你也实行了 必要的重新排序回调 继而自动将集合视图设为重新排序模式 (重新排序) (删除) 另一个例子是删除附件 以前被称为删除编辑控件 如果用户轻点了这个附件 列表单元格会自动显示 你的单元格的所有已配置的尾随轻扫操作
我们还有一个全新的附件:轮廓公开附件 当用户轻点这个附件时 单元格会自动与数据源进行交流 并展开或折叠这个单元的子级 这需要使用新的分组 snapshot API 你可以在 《差量数据源的改进》中了解到全部内容
现在我们来看 API 的工作原理 为了配置你的单元格附件 你只需将列表单元格上的单个附件属性 设置为 UI 单元格附件的区域 对于这个例子 我将用一个公开指示器 和一个删除附件来配置单元格 系统知道公开指示器 应该总是放在你的单元格尾随端 而删除附件总是在你的单元格前导端出现 所以 UIKit 将自动按照正确顺序为附件进行排序 并在相应端显示它们
此外 系统还知道 尽管公开指示器应该始终可见 但删除附件应该只在 集合视图处于编辑模式时可见 所以在进入和退出编辑模式时 UIKit 将自动把删除附件带进或带出
我们提供了许多诸如此类的系统默认值 不过我们使你能够 对其中的大部分进行自定义 例如 如果你想让公开指示器 只在非编辑模式时可见 只需将显示参数设置为 whenNotEditing 这是一个极其声明式的 API 其中的 UIKit 会为你打理好所有状态 如你所见 列表是一个高度可自定义化的布局 它十分模块化和灵活 它相当容易采用 所以请去查看示例代码并上手试用 你在里面会有更多的发现 而且一旦你熟悉了新的 API 就可以考虑 你能够在 app 的哪些地方增强布局 考虑你可以替换哪些地方已有的表格视图 并利用其灵活性 将列表 和任意自定义组合布局混合使用 当然 还请查看 我们关于 UICollectionView 的其它视频 iOS 14 中的集合视图还有更多精彩功能
非常感谢大家观看
-
-
3:47 - Simple Setup
// Simple setup let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) let layout = UICollectionViewCompositionalLayout.list(using: configuration)
-
4:25 - Per-Section Setup
// Per section setup let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment)
-
4:40 - Per-Section Setup full
// Per section setup let layout = UICollectionViewCompositionalLayout() { [weak self] sectionIndex, layoutEnvironment in guard let self = self else { return nil } // @todo: add custom layout sections for various sections let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment) return section }
-
5:49 - Header Mode Supplementary
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) configuration.headerMode = .supplementary let layout = UICollectionViewCompositionalLayout.list(using: configuration) dataSource.supplementaryViewProvider = { (collectionView, elementKind, indexPath) in if elementKind == UICollectionView.elementKindSectionHeader { return collectionView.dequeueConfiguredReusableSupplementary(using: header, for: indexPath) } else { return nil } }
-
6:51 - Header Mode Supplementary Optional Header
let layout = UICollectionViewCompositionalLayout() { [weak self] sectionIndex, layoutEnvironment in guard let self = self else { return nil } // check if this section should show a header, e.g. by implementing a shouldShowHeader(for:) method. let sectionHasHeader = self.shouldShowHeader(for: sectionIndex) let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) configuration.headerMode = sectionHasHeader ? .supplementary : .none let section = NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: layoutEnvironment) return section }
-
7:07 - Header Mode First Item In Section
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) configuration.headerMode = .firstItemInSection let layout = UICollectionViewCompositionalLayout.list(using: configuration)
-
11:40 - Swipe Actions
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Model> { (cell, indexPath, item) in // @todo configure the cell's content let markFavorite = UIContextualAction(style: .normal, title: "Mark as Favorite") { [weak self] (_, _, completion) in guard let self = self else { return } // trigger the action with a reference to the model self.markItemAsFavorite(with: item.identifier) completion(true) } cell.leadingSwipeActionsConfiguration = UISwipeActionsConfiguration(actions: [markFavorite]) }
-
14:55 - Accessories
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in // @todo configure the cell's content cell.accessories = [ .disclosureIndicator(), .delete() ] }
-
15:51 - Accessories w/ Parameters
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in // @todo configure the cell's content cell.accessories = [ .disclosureIndicator(displayed: .whenNotEditing), .delete() ] }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。