Diffable Data Source 的进阶用法
Advances in diffable data sources
2020年6月26日
一句话判断
iOS 14 为 Diffable Data Source 新增了 Section Snapshot 和 Reordering 支持——如果你还在用传统的 performBatchUpdates 管理列表数据变化,是时候升级了。
这场 Session 讲了什么
Diffable Data Source 在 iOS 13 引入,用快照(Snapshot)替代了传统的 insert/delete/reload/move 操作。iOS 14 带来了两个重要增强:Section Snapshot(按 Section 管理数据)和 Item-based Reordering(拖拽排序支持)。
Section Snapshot 允许你独立管理每个 Section 的数据层次。这在树形结构展示中特别有用——比如文件浏览器中每个文件夹就是一个 Section,可以独立展开和折叠。NSDiffableDataSourceSectionSnapshot 提供了树形数据的增删改操作。
Reordering 支持让 Diffable Data Source 可以与 UICollectionView 的 Drag & Drop 集成。通过 canReorderItem 和 reorderingHandlers 回调,你可以让用户拖拽排序列表项,数据源会自动更新 Snapshot。
值得深挖的点
Section Snapshot 与普通 Snapshot 的区别。 普通 NSDiffableDataSourceSnapshot 是扁平的——所有 Item 在同一层级。Section Snapshot 支持树形层次(parent-child 关系),可以递归地表示嵌套数据。对于大纲视图、文件浏览器这类需求,Section Snapshot 是更自然的选择。
Reordering 的数据一致性保证。 拖拽排序时,Diffable Data Source 的 reorderingHandlers.didReorder 回调给你一个 NSDiffableDataSourceTransaction,包含变更前后的 Snapshot。你可以在回调中将新的顺序同步到数据模型中,确保数据源和模型始终一致。
代码片段
import UIKit
// 配置支持拖拽排序的 Diffable Data Source
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(
collectionView: collectionView
) { collectionView, indexPath, item in
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "Cell", for: indexPath
) as! UICollectionViewListCell
var content = cell.defaultContentConfiguration()
content.text = item.title
cell.contentConfiguration = content
return cell
}
// 启用拖拽排序
dataSource.reorderingHandlers.canReorderItem = { item in
return true // 所有项都可以拖拽排序
}
dataSource.reorderingHandlers.didReorder = { transaction in
// 获取排序后的新数据顺序
let updatedItems = transaction.finalSnapshot.itemIdentifiers
// 将新顺序同步到你的数据模型中
saveNewOrder(updatedItems)
}
// Section Snapshot——管理树形数据
func updateOutlineView(with files: [FileItem]) {
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<FileItem>()
func addChildren(_ items: [FileItem], to parent: FileItem?) {
for item in items {
if let children = item.children {
// 有子项:递归添加
sectionSnapshot.append([item], to: parent)
addChildren(children, to: item)
// 默认折叠
sectionSnapshot.collapse([item])
} else {
// 叶子节点:直接添加
sectionSnapshot.append([item], to: parent)
}
}
}
addChildren(files, to: nil)
dataSource.apply(sectionSnapshot, to: .main, animatingDifferences: true)
}
// 展开/折叠处理
// 在 Item 上监听展开折叠事件
class OutlineCell: UICollectionViewListCell {
override func systemLayoutSizeFitting(
_ targetSize: CGSize,
withHorizontalFittingPriority horizontalPriority: UILayoutPriority,
verticalFittingPriority: UILayoutPriority
) -> CGSize {
// 使用 self.configurationState 判断是否展开
return super.systemLayoutSizeFitting(
targetSize,
withHorizontalFittingPriority: horizontalPriority,
verticalFittingPriority: verticalFittingPriority
)
}
}
最佳实践
- 新项目直接用 Diffable Data Source。 不要再用
performBatchUpdates,Diffable 方式更安全、更简洁、更不容易出 bug。 - 树形数据用 Section Snapshot,扁平数据用普通 Snapshot。 两者的 API 略有不同,选对了用起来更顺手。
- 拖拽排序后立即同步数据模型。
didReorder回调中拿到新顺序后,务必更新你的持久化数据,否则下次刷新会恢复旧顺序。 - 利用 Snapshot 的差异化更新。
apply时设置animatingDifferences: true,系统会自动计算数据变化并添加动画。
还有什么值得关注
- Section Snapshot 支持 expand/collapse 动画,自动处理树形结构的展开收起
- Diffable Data Source 的 Snapshot 支持
reconfigureItems方法,iOS 15 会进一步扩展这个能力 - UICollectionView 的 isPrefetching 可以配合 Diffable Data Source 实现预加载
- Section Snapshot 的
isVisible方法可以检查某个 Item 当前是否在可见区域