I'm using UITableViewDiffableDataSource in my project with UIKit. In some cases (e.g. moving item from one place to another) I need to use .reconfigureItems(_:) or .reloadItems(_:) to force DiffableDataSource to update certain cells. But the result of using those methods can lead to updating all items in UITableView. I found that when I'm applying snapshot using .apply(_:) all the previous items are still in the snapshot.reconfiguredItemIdentifiers property.
Here is a code example of setting up UITableViewDiffableDataSource and reproducing the problem.
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)
}
}
And Activity class looks like:
class Activity: Identifiable {
let id = UUID()
var title: String
init(title: String) {
self.title = title
}
}
If you tap first, second and third cells, the output will be kind of:
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
While I was expecting to have:
setting up cell with title: first tap
setting up cell with title: second tap
setting up cell with title: third tap
Can someone help me and explain what am I doing wrong?
Try creating a new snapshot from the previous one instead of directly updating the property-stored one as it's.
https://developer.apple.com/documentation/uikit/nsdiffabledatasourcesnapshot
Hopefully this solves your problem, if not then also consider having your model
Activityconform toIdentifiable and Hashableinstead of using theUUIDdirectly as hashable.If it persists, adding your own version of the
Equatablemethod==(lhs:rhs)may help the diffing algorithm properly identify your cells.Btw
Hashablealready conforms toEquatablebeing the scenes.