View in English

  • 打开菜单 关闭菜单
  • Apple Developer
搜索
关闭搜索
  • Apple Developer
  • 新闻
  • 探索
  • 设计
  • 开发
  • 分发
  • 支持
  • 账户
在“”范围内搜索。

快捷链接

5 快捷链接

视频

打开菜单 关闭菜单
  • 专题
  • 相关主题
  • 所有视频
  • 关于

更多视频

大多数浏览器和
Developer App 均支持流媒体播放。

  • 简介
  • 转写文稿
  • 代码
  • UICollectionView 中的列表

    了解如何使用 UICollectionView 在 app 中构建列表和边栏。 充分利用组合布局的灵活性,替换列表视图外观。 了解模块化布局选项,以及它们如何为你的 app 解锁更多设计选项。了解如何在单个 UICollectionView 内将类似于列表图的列表与自定义组合布局结合在一起。 了解如何使用列表,创建更丰富的单元格以及自定义布局,从而在 app 内创建设计合理的信息显示方法。 想充分利用本节内容,建议先基本了解组合布局。 获取更多信息,请从 WWDC19 观看“网格视图布局的新功能”。

    资源

    • Implementing modern collection views
      • 高清视频
      • 标清视频

    相关视频

    WWDC20

    • 为 iPad 打造
    • 差量数据源的改进
    • 最新单元格配置方法
    • UICollectionView 新发现

    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()
          ]
      }

Developer Footer

  • 视频
  • WWDC20
  • UICollectionView 中的列表
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载 (英文)
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    获取 Apple Developer App。
    版权所有 © 2025 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则