View in English

  • Apple 开发者
    • 入门汇总

    探索“入门汇总”

    • 概览
    • 学习
    • Apple Developer Program

    及时了解最新动态

    • 最新动态
    • 开发者你好
    • 平台

    探索“平台”

    • Apple 平台
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    • App Store

    精选

    • 设计
    • 分发
    • 游戏
    • 配件
    • 网页
    • Home
    • CarPlay 车载
    • 技术

    探索“技术”

    • 概览
    • Xcode
    • Swift
    • SwiftUI

    精选

    • 辅助功能
    • App Intents
    • Apple 智能
    • 游戏
    • 机器学习与 AI
    • 安全性
    • Xcode Cloud
    • 社区

    探索“社区”

    • 概览
    • “与 Apple 会面交流”活动
    • 社区主导的活动
    • 开发者论坛
    • 开源

    精选

    • WWDC
    • Swift Student Challenge
    • 开发者故事
    • App Store 大奖
    • Apple 设计大奖
    • Apple Developer Centers
    • 文档

    探索“文档”

    • 文档库
    • 技术概述
    • 示例代码
    • 《人机界面指南》
    • 视频

    发布说明

    • 精选更新
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • Apple tvOS
    • Xcode
    • 下载

    探索“下载”

    • 所有下载
    • 操作系统
    • 应用程序
    • 设计资源

    精选

    • Xcode
    • TestFlight
    • 字体
    • SF Symbols
    • Icon Composer
    • 支持

    探索“支持”

    • 概览
    • 帮助指南
    • 开发者论坛
    • “反馈助理”
    • 联系我们

    精选

    • 《开发者账户帮助》
    • 《App 审核指南》
    • 《App Store Connect 帮助》
    • 即将实行的要求
    • 协议和准则
    • 系统状态
  • 快速链接

    • 活动
    • 新闻
    • 论坛
    • 示例代码
    • 视频
 

视频

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

更多视频

  • 简介
  • 转写文稿
  • UICollectionView 导览

    UICollectionView 是一款灵活、功能强大的工具,能帮助您在 app 中打造出色的用户体验。了解如何利用这些丰富的 API 来快速地将最初的设计灵感转化成精致的成品 app。主题内容涵盖入门指南到高级更新动画和布局。

    资源

      • 高清视频
      • 标清视频
    • 演示幻灯片 (PDF)

    相关视频

    WWDC19

    • 集合视图布局改进
  • 搜索此视频…

    [ 音乐 ]

    [ 掌声 ] 下午好 欢迎来到 UICollectionView 之旅 我是 Steve Breen 我 把这个放在这 我是 UIKit 团队的一名结构工程师

    今天舞台上除了我 还有我的同事 Mohammed Jisrawi 他也是 UIKit 团队的

    所以今天我们将做 一些有点不同的事 我们从我们的设计师 Breanka[ 音译 ] 中获得了一些规格 我们将用这些规格建立一个 App 这将会应用到 UICollectionView 的许多功能当中 现在 当我们完成建立 我们的 App 需要的所有工作 我们将接触到 广泛的话题和 UICollectionView 有关 包括 布局 更新和动画 所以我们要涉及很多方面 让我们直接进入正题吧

    好 所以这是我们从设计师那里得到 的第一个规格 看起来像 FriendFeed 想象一下 一个很小的柱状布局 看起来很简单 好 好 这看起来很棒 所以我们看到了这个 看起来很棒的花式镶嵌布局 这正是我们的 FriendFeed 的内容 好的 Mohammed 既然你 将要为我们写所有 代码 快速教我们 怎样使用这些功能 你设计这些的想法 是什么 你知道的 我是 第一次看到这些 但是 它们看起来都是不错的 CollectionView 候选人 我认为用这个有 很多乐趣 好的 这看起来很棒 好 所以在我们钻研代码 Mohammed 开始带领我们 浏览之前 需要 涉及三个关于 CollectionView 我们 要理解的三个概念 然后再钻研代码 让我们开始吧 我们 将要谈论布局 数据源和委托 好的 所以 首先让我们 谈论一些关于布局的事情 如果你第一次 开始研究 UICollectionView 的定义 而且你对 UITableView 很熟悉 你会立马注意到在 API 中有很多熟悉的地方 你有一个委托和一个数据源 所有这些看起来都很 熟悉 但是这个布局 概念很独特 而且不同于 UICollectionView 你可以把它看做 UICollectionView 的超能力

    它允许 CollectionView 从内容中抽象出 与内容本身分离的 视觉安排

    布局是所有关于 内容安排在哪里的事情

    现在 每个单独的项都是由 UICollectionView 的 布局属性 指定 比如像 界限 中心和帧这样的属性 你可以把它看做一套 你可以使用的特性 用来定义 这些可以展示的项目 你甚至可以自定义它们 通过 打印你的 UICollectionView 布局 属性中的子类 把这些都包含在你的 设计当中

    好 所以当用户 滚动浏览屏幕上的 内容 布局被认为是不能改变的 现在如果你需要改变这个 比如你将要 改变一个布局的外观 你要使用 无效化机制 Mohammed 将会带领我们 快速浏览一下 好的 现在 关于布局是一个 单独的抽象的很棒的一件事是 我们可以 从一个布局转换到另一个 布局中 当你在不同布局中 移动 会有一个动画效果 布局 A 不必 知道任何关于 布局 B 的事情 它们只是 声明这个布局将变成什么样子 然后转换就发生了

    好 所以 CollectionViewLayout 是 一个抽象类别 正因如此 这意味着不能直接 被使用 但是 CollectionViewLayout 的 子类可以直接被使用

    幸运的是 我们会提供一个 UICollectionViewFlowLayout 如果你之前用过 CollectionView 你可能对它很熟悉 现在 CollectionViewLayout 上 有很多定制点 包括一些我们一会儿要讲的 特性 但是你 也可以使用委托进行 自定义 我们马上 会谈论到 CollectionViewDelegate 但是 CollectionViewFlowLayout 将会 指定一些额外的东西 扩展到 CollectionViewDelegate

    好的 所以 Flow 是关于什么的 它是一个基于线的布局 系统 正因如此 它可以包括多种 你可能享受到的不同设计

    让我们浏览一下 解释什么是基于线的系统 的最好方法是像这样 给出一个例子 让我们开始吧 好的 所以在这我们可以看到 我们得到了 一个垂直的滚动集合 视图 我们将模拟 Flow 布局是什么样的 当它展示出这个内容时 好的 这是我们的第一个项目 我们 在顶部的前沿开始 我们开始沿着一条线 布局我们的项目 现在看这条线 这条线和滚动轴垂直 我们垂直滚动 所以这个线是水平的

    好了 现在注意到 在这条线的空白处 填满了项目 我们将 下降到另一条线 继续布局我们的内容 最终 我们落到了 最后一条线 我们得到了所有的内容

    现在如果我进行一些指示 00:05:38.596 --> 00:05:39.976 A:middle 来突出显示

    这些水平线的地方 让我们谈谈一些已有的定义 作为自定义 Flow 的方法 首先是行间距的概念 正如你在这看到的箭头 行间距将要成为 这些水平线之间的间距

    相似的是 项目之间的间距 指的是这些沿着这个布局线 项目之间的间距 在 Flow 布局中我们有两个属性 让你指定 他们之间的最小值 好的 所以让我们巩固一下 我们的直觉 然后旋转整个画面 让我们开始于 顶部的前沿 现在 这个是水平滚动 好了 所以我们 将要画一个垂直的布局线 当我们到达这个区域的底部 我们就填满了这条线 回到顶部 好的 这个图案现在非常熟悉了 继续 这是我们的所有内容 现在我们有了垂直布局线 所以按照这个方向 我们的 行间距是这样的

    我们的项目之间的间距是这样的 当你在使用 Flow 布局时 记住这个很关键

    好的 所以这就是布局 让我们谈论一点 数据来源 如果你使用 TableView 这个应该看起来 很熟悉 这是一个很简单的图案 他们分享相似的 API 好 所以如果布局是 关于内容摆放在哪里的所有事情 数据源就是内容是什么 内容本身是什么 有三种核心办法以供思考 第一个是可选的 在 CollectionView 中的许多部分 如果你不提供这个 我们就会认为你有一个

    相似的是 我们在这一部分 有很多项目 这将会告诉你 每个单独部分的项目数量 因为它们可以有所有不同的项目 最后一个 出售 项目索引路径就是 你为你的客户提供 你将要展示的真实内容 好的 这就是数据源 好的 在我们和 Mohammed 钻研 代码之前 我们将要谈论的 三个话题中的最后一个是委托 好的 所以使用委托是可选的

    现在 CollectionView 是 UIScrollView 中的一个子类 所以我们使用 由 ScrollView 超类 提供的相同委托 但是我们扩展它 所以如果你需要修改 滚动行为 你可以 在相同的委托中来进行 也可以使用 一些 UICollectionViewDelegate 中的 方法 提供一些细粒度控制 当用户和你的内容交互时 控制高亮和选中 我们也会进入一个 API 让你知道 有一些东西 出现在屏幕上 WillDisplayItem 和 DidEndDisplayingItem 好的 所以在我们钻研代码 开始 UICollectionView 之前 有三个重要概念 我们需要谈论一下 所以让我们切换到 Mohammed 的 Dev Box 让他向我们展示下它是怎么工作的 Mohammed 好的 所以 两个列布局屏幕中的第一个 就是使用 CollectionViewFlowLayout 的 一个绝佳案例 我们可以完成 我们需要的任何事情 这也会是我们 开始使用 UICollectionView 的一种很好方式

    所以现在当我们可以用一个 Flow 布局 完成整个设计目标时 我实际上 是来到了 CollectionViewFlowLayout 的 子类因为 我们将要再做一些 额外的定制化服务 所以我将要开始

    创立一个我的 ColumnFlowLayout 分类的实例 我准备好了 我将要使用这个实例 来建立我的 CollectionView 我将要移动这个 CollectionView 并且 设置一些视图属性 比如 自动调整掩码 背景 颜色 因为它是 ScrollView 我也可以设置 一些 ScrollView 的属性 这一切只是为了让它 看起来和感受起来就像我想为 大家在我们的 App 上所呈现的那样 在把 CollectionView 添加到 我的视图层以后 我将要 注册我的 PersonCell 类 在 CollectionView 上使用独特的 标识符 所以我们可以 在 App 中设计单元格 然后我将要建立 这个 View Controller 作为这个 CollectionView 的数据来源 所以 我们可以提供给它一些信息 关于将要展示多少单元格 以及在它的单元格上将要 展示什么样的数据 接下来我将要把它设置为 委托 所以我们可以 处理单元格的选择 所以既然我们已经建立起来这些 我们实际上需要遵循 这两种协议 所以让我们先遵循 数据源 并且我们有 两种要求我们执行的方法 这些当中的第一个是 Section 中的项目数 回到我们人口布阵 当中的人数或者项目数 来展示我们的 数据模型体 第二个我们需要执行的 方法是 CellForItemAtIndexPath 我们可以从 CollectionView 中脱离 单元格队列使用我们独特的 标识符 穿过一个 从我们的人口布阵脱离出去的 人和物体 来到单元格展示 我们的数据 然后 回到单元格

    事情到这圆满完成 我们接下来只需从 委托协议中挑选一种 可供选择的方法 所以 我们可以处理选择 所以我们将要添加 DidSelectItemAtIndexPath 在其中我们将用示例 讲解 FeedView 控制器这 将要成为我们的第二屏幕 如果 我们没有一个实例 接下来我们需要 穿过一个人 所以我们 知道要展示谁的图像 接下来我们要把它放到 我们的导航控制器当中 好的 所以让我们建立这个 然后转换到模拟器中来看看

    好的 [ 掌声和欢呼声 ] 好的 所以可以看到 屏幕上有了我们的 CollectionView 我们也有了一些单元格 你可以看到它们 虽然它们 是扁平的 它们的大小并不合适 所以 我们将对它们做一些 我们认为我们需要做的 自定义 所以让我们回到 Xcode 中 让我们在这打开我们的 子集列 我们之前放在一起的 ColumnFlowLayout Class

    让我们看看我们 在这需要做什么 所以我早已准备了 一种重写布局存根的方法 现在不论什么时候布局无效 就调用 UICollectionView LayoutsPrepare

    只要 CollectionView 的边界大小改变 我们的布局就无效了 所以如果我们的 App 在手机上旋转 或者我们的 App 在 iPad 上重新调整大小

    所以在这里进行自定义 很棒 这种自定义把 CollectionView 考虑到其中了 在我们的例子中 我们想要我们的单元格 拥有像 CollectionView 一样的 很多功能 我们也可以让 CollectionView 知道我们想要 我们的项目变多大 通过 指明项目的大小属性 所以我将要继续 在这里做这个

    所以我要把我的 CollectionView 的项目大小 设置为 CG 大小 宽度也设置成 CollectionView 的边缘宽度 插入布局边缘 我们将要把它的高度 设置成 70 点 以此来匹配我们的设计

    既然我们已经到这了 我将要在这做 一些不同的事情 使它们看起来美观 我要在顶部 填充一个 sectionInset 来匹配我们的项目内 间距 我将要把这个布局的 sectionInsertReference 属性设置到安全区域 所以一切都整齐地 安置在 CollectionView 的 安全区域内 好的 让我们再一次 回到模拟器中 看看 我们正确构建的 布局长什么样子 好的

    这看起来很棒 这看起来就像我们的规格 我认为我们的设计者将会非常开心 如果我们把它旋转成横屏 我们可以看到我们的单元格大小很合适 所以我们知道我们的 无效代码 随时准备再次被调用

    现在一切都准备好了 你可能会想 我们并没有做到最好 它看起来没有非常棒 我们可能想要做一些 更有趣的事情 比如 展示多列布局 因为我们有了一些可获得的间距 现在流式布局使这个 变得如此简单 如果你记得之前 Steve 的解释 流式布局是怎样 安排它的布局 流式布局会在 移动到下一条线之前 安装尽可能多的项目 所以使用这个 我们可以 搞清楚布局 如果我们改变我们的项目大小 我们就可以获得很多列 所以如果我们回到 Xcode 中 回到我们这里的布局 如果我们仅仅改变我们计算项目 大小的方式 所以我将要移除 这个 将要 用一些和数学相关的东西 来替代它

    所以我将要开始使用 与我之前获得的相同的可用宽度 这是由边距插入的边界 和一些对最小列宽的 任意定义 是 300 点 然后取这些数值中的 两个数值 使用它们 来计算一个我认为可以安装 在可用空间中的 最大列值 我将使用这个数值来除以 可用宽度来 计算出一个最佳单元宽度 这可能会超过 300 点 我将要把这个数值带到 我现在正在使用的作为我的项目 大小的 CT 大小中 好的 所以现在让我们再次回到 我们的模拟器中 看看我们 更新的布局是什么样子

    好的 所以一切都一模一样 我们没有打破它 这是一个好的开始 如果我们 想要横过来 我们就会有多列并排 这正是我们想要的

    你怎么想呢 Steve 看起来很棒 我们得到了一个很棒的可改变的 柱形布局 没有花太多力气 没有 接下来我们的设计是什么呢 好的 既然我们已经轻松 进入我们的朋友列表中 现在该开始 想想 App 的花式镶嵌布局了 好 很棒 是的 让我们回到 幻灯片 稍微 聊一聊 好的 让我们看一下 这里的布局或者设计 看看我们可以做什么 所以我们的第一个意向 我不知道你的 但是我的是 我能否用 Flow 我懂了 准备好了 让我们开始使用它

    所以让我们简单看一下 这个设计 看看 Flow 对于 我们来说是否有意义

    在这个特殊的区域有 这三张照片 我将迅速放大 好

    所以在现在这个例子中 我们左边有一张非常大的照片 右边有一个垂直的堆叠 所以在 Flow 领域 因为它是基于线条的 我们将要 把左边那个大一点的项目 移动到下一个项目 所在的地方 然后再安排 另一个项目 然后再跳到 下一条线中 但是我们没有完成 我们还要处理这个 垂直的堆栈 所以这对于 Flow 来说 行不通 因为事实证明 它并不是一个真的基于线的布局

    但是这个练习的经历 还是有用的 所以 你知道 让我们先开始 Flow

    好的 所以在这个例子中 我们将要创建 我们自己的自定义布局

    我们有点害怕 不用怕 这并不复杂 我们已经有了四种基本方法 来处理这个 我将要 提出另外一种 让人自豪的方法

    好的 四种方法 让我们开始吧

    我想说的第一种方法 关于 CollectionView 的内容大小 现在 在我们提到 CollectionView 是 UIScrollView 的一个子集之前回忆 一下 UIScrollView 的 一个特征就是 你有一个可视化区域和一个 很大的内容区域 并且你有一个 很棒的 iOS 体验 可以在其内部移动你的内容 所以 CollectionView 需要知道怎样 告诉 ScrollView 我的内容有多大 好的 所以我们如何得到这个尺寸 想象一下一个长方形 包含这个布局将要 为你的 CollectionView 定义 所有内容 我们想要这样的大小 好的 这就是 CollectionView 的内容大小

    接下来我们有两种 提供布局属性的方法

    第一种是 LayoutAttributesForElements(在 矩形中)

    现在这个被 CollectionView 定期调用 当用户第一次滚动浏览 你的内容或者展示时 它需要知道在屏幕上展示 什么 所以这个查询是由一个几何区域构成的

    好的 这是 API LayoutAttributesForItem AtIndexPath 你可以想象 它只是在寻找一个单独的项目 给我这个属性

    好的 当 Mohammed 带着我们浏览的时候 我们会看到更多东西 但是对于这两个 API 来说 需要注意的是 性能最重要

    好的 所以我们四个 核心自定义布局子集中的第四个 就是 Prepare 方法 现在 Mohammed 已经讲了 一些这方面的事情 每当布局无效时 就调用这个 所以这是计算一切的 有利时机 比如 你想要缓存的布局属性 和你的内容大小 之后很快就会要求这些事情

    好的 所以我们值得骄傲的 API 让我们谈论一下它吧 这是一个为边界变更设置的 ShouldInvalidateLayout 所以每次 CollectionView 的 边界改变时 都会被调用 好的 再一次 它是 CollectionView 是 UIScrollView 的一个子集 所以边界发生变化 究竟是什么意思 当一个 ScrollView 边界发生改变 原点可以在滚动期间 发生改变 当 App 大小改变 或者 CollectionView 的大小改变时 其大小也会发生改变 所以这将会在滚动过程中被调用

    是的 就像表情符号 这经常被调用 所以在这里做出正确的决定 非常重要 好的 所以 UICollectionViewLayout 中的 默认执行将返回 false 所以如果你需要让它做 一些不同的事情 你的机会到了

    作为示例的一种方式 如果原点改变 UICollectionViewLayout 将返回 false 好的 所以用户只是 在滚动浏览你的内容 我们不会无效 把它变成默认状态 但是如果 iPad 旋转 手机旋转 你的 App 大小就发生了改变 它即将返回 true

    现在有一点例外 比如浮动的页眉和页脚 对吧 当你在滚动你的内容时 我们必须重新计算这些 要做一个自定义的无效 来考虑这些事情 好的 理论已经足够多了 让我们回到我们的 开发机器当中 让 Mohammed 带领我们了解一下 在用代码建立这个奇特自定义 UICollectionViewLayout 的时候 看起来会是什么样子

    好的 让我们直接进入 正题吧 所以我已经把 另一个我们将要为这个布局使用的 布局子集放到一起 你可能会注意到 它是 UICollectionViewLayout 的 一个子集 不是 CollectionViewLayout 的 一个子集 这就是 Steve 之前向我们 解释的原因 UICollectionViewLayout 并没有 真正满足我们 自定义的镶嵌布局设计

    所以我在这做的第一件事 是我要建立一些 实例变量 我将用它们来保存 一些稍后可以参考的 关键信息

    第一个是内容绑定的 CG 矩形 我将要 用它来保持 我的 CollectionView 内 所有项的代表性边界 第二个是缓存 属性数列 我将要使用这个来保存我的 布局属性 所以 当性能重要的时候 我可以快速查阅它们

    所以我们将为这个布局 再次开始执行我们的 Prepare 类函数

    Prepare 是我们做大部分工作的 理想场所 因为每次失效都会调用 Prepare 我们可以在这建立我们自己的布局 然后避免做任何 繁重的布局工作或者任何繁重 的经常被调用的 布局数学函数

    所以我们要在这儿做很多事情 首先 我们将重置我们的 缓存属性和我们的 内容界限只为了清除 以前失效的 所有陈旧信息

    接下来 我们要为 我们的 CollectionView 中的每一个项目 做一些事情

    这些当中的第一个实际上是 准备这些属性 但是现在我不会 深入研究 具体布局的内容 因为 这对你而言 会有所不同 这是你将要 计算大小 位置和转换的地方 使你的单元格 符合你的设计需求 但是在我们完成 这些属性以后 我们将要在这做很多关键的事情 第一 我们将要缓存它们 我们将要把它们放在我们的 缓存的属性数组以便我们 不久后可以快速抓住它们 第二个是 我们将 把他们的框架和我们的 内容界限矩形结合起来 以便于我们的内容界限保持更新

    所以既然我们的 Prepare 已经出现 并且运行 我们需要 在我们需要使一切运转的 布局中执行 剩余的方法 所以这些当中的第一个 CollectionView 内容大小 在这其中 如果我们在 Prepare 中正确地 完成了我们的工作 我们可以 返回我们的内容界限作为大小

    下一个是为边界改变设置的 shouldInvalidateLayout

    现在既然我们的布局中不 包含任何当我们滚动时 失效的元素 所以没有任何浮动的 标题 浮动页脚或者 类似的东西 我们只想在 CollectionView 的大小变化范围 发生变化时无效 所以如果我们的新的大小边界 不等于 我们的 CollectionView 的大小边界 我们目前的大小边界 我们就会返回 true

    在那之后 我们将执行 LayoutAttributesForItem AtIndexPath 在这里 再一次 因为在我们的 Prepare 函数中 我们已经准备了所有属性 我们只能把握 从我们的数组中回应 RequestAtIndexPath 的特别属性 最后 我们将执行

    LayoutAttributesForElements 00:24:23.786 --> 00:24:24.166 A:middle InRect

    现在这个方法 被带有不同 查询矩阵的 CollectionView 定期调用 这个矩形可能会比 我们的 CollectionView 更大 我们的 CollectionView 只要求一些列 匹配特定区域的属性 我们的工作是返回一个数组 该数组包含所有属性 这些属性与所有 将要在我们的 CollectionView 矩形中出现的 所有项目相对应

    所以我们可以简单地 通过过滤属性框架上的 缓存属性数组 来回答这个问题 所以如果我们的属性 有一个与我们的查询矩阵相交的框架 我们可以返回它们 好的 所以让我们回到 Sim 看看我们的布局变成了什么样子 所以我将要挑选其中的一个 Feed 在这 我们有我们的布局 我们的图像可以很好地加载到 这个花式镶嵌结构中 如果我们想要旋转成横屏 你可以看到我们的单元格已经 调整了大小 所以我们已正确更新 所有内容 我们已经将其 无效化 这真的很棒

    所以这看起来很像我们的规格 但是滚动性能并不是很好 对吗 不是很好 很差

    所以你可能早已有了一个 关于接下来怎么进行的主意 让我们回到代码中 看看接下来会发生什么

    所以如果我们在 这的矩形中看看我们的布局属性或 元素 要记得这种 方法在滚动时 经常被调用

    所以这里的这个函数 正在过滤我们的整个数组 你可能会想 当我们的 CollectionView 的项目数增加 会变得非常高耗 所以在我们的 App 中 我们的照片越多 我们的滚动进程将会越慢

    所以如果你发现你自己遇到 这种情况 它帮助你 后退 想想你 布局的属性 想想 你是否可以找到任何 优化机会 所以我们的布局要求 每个单元 App 紧邻或者 低于它的前一个单元格 所以这意味着我们的 属性已经按照 它们框架的最小 y 值 在缓存的属性数组中排序了

    所以我们有一个排序数组 所以我们 可以通过做一些 二进制的搜索来加速搜索 而不是像现在我们正在做的 线型过滤器 所以让我们删除我们的 慢执行 让我们 用一些本应该更快的东西来替代它

    所以我会逐步介绍 不必担心 所以我们在这要做的 第一件事是我们调用 我们已经准备好的 二进制搜索函数 它在我们的数组和 我们的查询矩阵中 引入了一些列索引

    如果它发现了一组 在我们的矩阵中包含一个框架的属性 它将作为我们数组中的索引 返回属性 然后从该索引开始 我们可以简单地通过在 我们的数组中上下循环 并拾取属性 来构建其余部分 的属性集 直到我们退出查询矩阵 直到我们找到 在我们的矩阵之外的属性 这应该快得多了 在你的数组中 有成千上万的项目 你不可能通过成千上万个项目 循环成千上万次 好的 让我们再次回到 Sim 中 让我们看看 更快的滚动算法是什么样子

    让我们把这个弹出来的打开 然后轻点一下 更快了 你怎么看 Steve 好多了 好的 很棒 所以我们现在已经有了 两种布局 接下来是什么呢 所以我们有了两个屏幕 这位我们这里的 朋友列表留下了我们的 更新动画 很棒 好的 好的 让我们回到 幻灯片中 让我们浏览一下 我想我们的设计师会称它为 非常酷的更新动画 好的 这里有一个视频 让我们浏览一下 看看这个非常酷炫的 更新动画是什么样子 好的 所以我们这里有一些元素 我们看到最后一个项目更新了 我猜有人发了一张 照片 我们有在这得到了另一个项目 它看起来 是第三个项目涂片 不会在这里 好的 所以我们在这里有 三个基本操作正在进行 对吧 我们重新下载 移动 删除 我们为什么不回到 开发机器 Mohammed 你何不给我们展示一下 这个是怎么操作的呢

    当然 好的 所以我们 同时做了很多动画的更新 所以你可能会认识到 UICollectionView 和 UITableView 提供给 我们 一个多么伟大的工具 正是执行批量更新 API 它基本上可以允许我们 通过集合视图 可以在动画执行的同时 执行一组更新 所以我要给 CollectionView PerformBatchUpdates 添加一个调用 并且注意 我在关闭这里做了 我的数据源更新和 我的 CollectionView 的更新 这实际上是一种最佳方式 协调我的更新 并保持事物的整齐同步 避免事物不一致

    所以 首先 我在我的数据源上 更新了我的最后一个项目 我把第二个项目移动到最后 一个项目中 选中最后一个项目 移动到最顶端 然后我要求 CollectionView 执行我想要的动画 好的 让我们再次回到 Sim 看看我们的更新是什么样子 所以我们已经通过 右上角的更新按钮 连接了我们的更新代码 怎么样了

    有点尴尬 这发生了什么 你知道的 我写 iOS 很长时间了 我之前见过这个影片 是的 在舞台上发生这种事情 很糟糕 你知道的 我们的时间马上要 用完了 我们为什么不 调用重新下载数据 我们可以 回去 然后为 V2 做动画 真的吗

    [ 掌声 ] 你知道的 我们可以的 但是 接下来我们将会丢失这个非常酷炫的 更新动画 我们的用户 期待这些生动的界面 对吗 是的 你说的对 你知道吗 他们值得更好的 我喜欢你思考问题的方式 好的 让我们快速 回到幻灯片中 让我们看看我们是否保存了我们 非常酷炫的动画更新

    你之前已经看过了 好的 所以首先让我们仔细研究一下 这个调试异常 然后看看 它试图想告诉我们什么 好的 所以这里说的是 我们 正试图从 0-3 的 相同索引路径执行删除和 移动操作 所以如果我记的对的话 这刚刚是 第四个项目 我们重新下载 然后移动它 我们刚刚没有删除它 我们删除了 第三个项目 0-2 对

    所以 这是怎么回事 好的 在我们开始这个之前 让我们回去 匆匆看一眼 PerformBatchUpdates API 讨论一些高级原则 好的 所以正如 Mohammed 之前提到的那样 当他介绍这个 API 的时候 这个 API 的目的便是 我们可以同时处理多种更新 让一切充满生气 获得这样一种 很棒的体验 也正如他所提到的那样 在 CollectionView 更新关闭中 执行数据源更新和 你的 CollectionView 更新非常重要 现在 我所说的关于 CollectionView 的这些也同样适用于 TableView 所以如果你想要在你的 App 上 安装 TableViews 所有的这些信息 都是一致的

    好的 让我们多观察一下 CollectionView 更新时 你插入 移动和删除 这些顺序 在你的更新关闭中 并不重要 把它们放在任何你想要放的地方

    然而 当你改变结构时 现在你的数据源会更新 提供你的数据源或者 支持数据源的数据源很重要

    好的 最好用一个例子 来展示 所以我将 列举一个例子 这个例子包含两个数组 每个数组有三个元素 我们将强化我们这方面的直觉 展示一个删除和一个插入 但是首先我们将 先执行一次删除 然后再执行第二个删除 我们将颠倒顺序 只是为了 增强我们的直觉 我一直这样做 画画 对 好的 所以我们删除第一个项目

    然后我们现在要在索引一插入 好的 在第二个例子中 我们颠倒顺序 先插入然后再进行删除 所以我们的直觉依然存在 确实 我们得到了一个完全不同的结果 这可能并不是一件好事 好的

    让我们把这个和 CollectionView 的更新做一下对比 现在这里有两组 批量更新提交的 CollectionView 更新 我已经删除了数据源更新 只是为了保持幻灯片的整洁

    但是我在第一个上面做了插入和 删除 在第二个上面先删除再插入 顺序是不同的 但这会给你完全 相同的结果

    我们都是工程师 我们想知道为什么 为什么是这样 好的 让我们谈谈吧 这是怎么发生的呢 为什么排序对于 发送到 CollectionView 的 更新并不重要 当然这是针对你的数据源而言的 好的 让我们通过操作 来完成这些操作 所以第一个是要删除 这是按降序索引路径顺序 进行的过程 现在让我们谈谈索引路径 所以首先 在批量更新开始之前 你在考虑 在 PerformBatchUpdate 中发生了什么 你的数据源处于之前状态

    现在一旦所有事情 在批量更新中完成 你将处于一个后续状态 好的 所以对于删除而言 索引路径总是指向前级 所以这就是删除 所以插入是按 升序索引路径顺序进行处理的 因此 插入中的索引 路径总是指向 最终状态或者 后更新阶段

    所以 移动是这两者的结合 对吧 你有一个来和一个去的索引 路径 来是处于 之前状态 好的 去是处于后续状态 重新加载 现在如果你愿意 重新加载算一个超级命令 它实际上可以分解成为 一个删除和一个插入 重新加载中指定的 索引路径是关于前状态的

    好的 所以既然我们已经 了解了重新加载是干什么的 这种洞察力可以告诉我们 在我们的 App 中 发生了什么错误

    因为最后个项目 重新加载中的删除和 项目的移动概念相冲突 好的 所以当我们回到代码中 我们可以一分钟内解决这个问题 好的 我不打算检查 这些 但是你可以稍后解释 只是把它放在这里作参考 这些都会导致 CollectionView 崩溃 不要这样做 我们如何获取所有这些 知识并简化它 以这样一种方式来使用它 我们总是可以从给定的 CollectionView 或 TableView 中应用我们的数据源更新 并确保所有的东西都同步 好的 所以这是四项基本的原则 所以首先你想要 分解这些动作 删除然后插入 简单 将所有删除和插入 组合到两个 单独的列表中 首先在索引路径上 按降序顺序进行删除 最后按升序索引路径 应用这些插入 做到这一点 就很好往下进行了 重新加载数据怎么样 我知道 Mohammed 说我们 可以做到这一点 我们完成了 每个人都笑了 我很确定我大喊了 情况就是这样 但是关于 重新加载数据的事实却是你没有 获得这些很棒的动画 这真的是一个强有力的做法 所以 我们真的想让我们的 App 变得活泼生动 使我们的客户 获得很棒的感受 所以 这是在特殊情况下使用的 好吧 Mohammed 让我们快速 切换回来 看看我们能否 在代码中解决这个问题 并保存完全酷炫的 更新动画 好的 是时候赎罪了 是的 所以让我们使用 刚刚 Steve 分享给我们的 指导原则来修复我们的更新动画 所以让我们删除这里的 所有执行 如果你还记得 我们的更新 包括重新加载 删除 和移动 我们的重新加载和移动 处于相同的索引路径 它们在同样的地方开始 这就是我们的冲突所在 所以我们需要先分开这两个 因此让我们重新载入 自己的调用来执行 批量更新 然后在这 我只是在更新我的数据源 再一次 和之前一样 在 CollectionView 上 调用重新加载的项目 我只是在没有动画关闭 的 UI 视图中执行它 如果你仔细查看 我们的规范 它实际上是非动画的 初始重新加载

    好的 所以接下来 我们必须 处理剩余的 更新 删除和移动 让我们来解释一下 我们在索引二处删除 然后把项目从索引三 移动到索引零处

    因此如果我们使用 我们刚刚学到的指导原则来 分解我们的动作 这就变成了 索引二进行删除 索引三进行删除 把项目插入到索引三至索引零

    所以现在我们有两套操作 我们有删除和插入 我们可以相应地处理它们 首先 我们按照降序顺序 执行删除操作 因此我们首先要在索引三处 进行删除 然后 抓住来自那里的人 以便 以后我们可以插入它们 接下来我们将要删除 在索引二处的项目 接下来我们需要按照升序的顺序来 处理我们的插入 我们只有一个 所以我们就 继续 插入它 最后 我们会要求 CollectionView 执行 我们想要的动画 现在 注意 我仍在调用 此处的移动 我没有把它分解成 它的组件动作 因为 我们仍然想要 Collection View 播放正确的动画 如果我们用我们的数据源 做出了正确的事情 那么 CollectionView 会根据动画做出 正确的思考 好的 让我们回到模拟器中 看看当它工作时 我们的更新是什么样子 好的 这里什么也没有 哇 很棒 [ 掌声 ] 我将要重新加载 绕场慢跑一圈庆祝胜利 让我们继续吧 这看起来和我们的规格完全一样 不是吗 很棒 了不起 好的 让我们圆满完成它 我们涵盖了大量内容 你能回到幻灯片中吗

    我想发布一个调用来执行 所以如果你对构建自定义布局 感到紧张或焦虑 请采用我们今天刚刚 应用的内容 然后返回 研究并建立那些自定义 布局 建立行之有效的 CollectionView 解决方案 如果在你的 App 中 重新加载的数据导出都是 你正在失去 这些灰色的动画 它们会检查这些事情 看看为什么你不理解这些 或者不明白 为什么会发生这些事情 并修复这些地方 好的 所以想获取更多消息 你可以看到幻灯片这儿 有链接 并且明天上午 9 点 我们也有一个 CollectionView 的实验 如果你有任何 关于你的 CollectionViews 的问题或评论 请顺便来 找我们聊一聊 Mohammed 和我都会在这 谢谢大家来参加 我希望你们能享受 接下来的会议 [ 掌声 ]

Developer Footer

  • 视频
  • WWDC18
  • UICollectionView 导览
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • Apple 智能
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • Mini Apps Partner 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。
    版权所有 © 2026 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则