求助:UITableViewDiffableDataSource应用快照后reconfiguredItemIdentifiers未清空
UITableViewDiffableDataSource调用reconfigureItems后全部条目被更新的问题
问题现象
在UIKit项目中使用UITableViewDiffableDataSource时,调用.reconfigureItems(_:)或.reloadItems(_:)想要仅更新特定单元格,但操作后整个UITableView的所有条目都会被重新配置。排查发现调用.apply(_:)应用快照时,snapshot.reconfiguredItemIdentifiers属性仍保留着之前操作的条目ID。
复现代码
ViewController代码
class ViewController: UIViewController { @IBOutlet var tableView: UITableView! var activitiesDataSource: [Activity]! var snapshot = NSDiffableDataSourceSnapshot<Int, Activity.ID>() lazy var diffableDataSource = UITableViewDiffableDataSource<Int, Activity.ID>(tableView: tableView) { tableView, indexPath, itemIdentifier in let activity = self.getActivity(by: itemIdentifier) print("setting up cell with title: \(activity.title)") let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell cell.titleLabel.text = activity.title return cell } override func viewDidLoad() { super.viewDidLoad() activitiesDataSource = [Activity(title: "first"), Activity(title: "second"), Activity(title: "third")] tableView.delegate = self tableView.dataSource = diffableDataSource tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell") prepareSnapshot() } func prepareSnapshot() { let activitiesIds = activitiesDataSource.map(\.id) snapshot.appendSections([0]) snapshot.appendItems(activitiesIds) diffableDataSource.apply(snapshot) } func getActivity(by id: UUID) -> Activity { guard let activity = self.activitiesDataSource.first(where: { $0.id == id }) else { fatalError("Could not find activity by id") } return activity } } extension ViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let activityId = snapshot.itemIdentifiers[indexPath.row] let activity = getActivity(by: activityId) activity.title += " tap" snapshot.reconfigureItems([activityId]) diffableDataSource.apply(snapshot) } }
Activity类代码
class Activity: Identifiable { let id = UUID() var title: String init(title: String) { self.title = title } }
实际输出与预期
依次点击第一、第二、第三单元格后,实际输出:
setting up cell with title: first tap setting up cell with title: first tap setting up cell with title: second tap setting up cell with title: first tap setting up cell with title: second tap setting up cell with title: third tap
预期输出:
setting up cell with title: first tap setting up cell with title: second tap setting up cell with title: third tap
问题原因
你复用了全局的同一个snapshot实例,没有每次创建新快照或重置快照的配置标记:
- 每次调用
snapshot.reconfigureItems(_:)后,该快照的reconfiguredItemIdentifiers会持久保存这些需要重新配置的条目ID。 - 当你再次调用
diffableDataSource.apply(snapshot)时,之前的标记并未被清除,导致DiffableDataSource认为之前标记的条目也需要重新配置,最终所有历史标记过的条目都会被重复设置。
解决方法
推荐方案:每次操作创建新的快照实例
DiffableDataSource的设计核心是基于快照状态的对比,正确的做法是每次更新时创建新的快照,或者基于当前数据源的快照进行修改:
- 修改
prepareSnapshot方法,每次创建新快照:
func prepareSnapshot() { var snapshot = NSDiffableDataSourceSnapshot<Int, Activity.ID>() let activitiesIds = activitiesDataSource.map(\.id) snapshot.appendSections([0]) snapshot.appendItems(activitiesIds) diffableDataSource.apply(snapshot) }
- 修改
didSelectRowAt方法,基于当前数据源的快照进行修改:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // 从数据源获取当前的快照,而不是用全局快照 let currentSnapshot = diffableDataSource.snapshot() let activityId = currentSnapshot.itemIdentifiers[indexPath.row] let activity = getActivity(by: activityId) activity.title += " tap" // 创建新快照并标记需要更新的条目 var updatedSnapshot = currentSnapshot updatedSnapshot.reconfigureItems([activityId]) diffableDataSource.apply(updatedSnapshot) }
备选方案:复用全局快照时重置配置标记(不推荐)
如果坚持复用全局快照,每次调用reconfigureItems前需要清除之前的标记,但这种方式容易遗漏其他状态,不推荐:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let activityId = snapshot.itemIdentifiers[indexPath.row] let activity = getActivity(by: activityId) activity.title += " tap" // 先清除之前的所有reconfigure标记 snapshot.reconfigureItems([]) // 再标记当前需要更新的条目 snapshot.reconfigureItems([activityId]) diffableDataSource.apply(snapshot) }
验证结果
修改后再次依次点击三个单元格,输出将与预期一致,每个单元格仅会被配置一次。
内容的提问来源于stack exchange,提问作者Alexei
相关产品推荐
相关产品推荐

