I have a hard time to understand how DiffableDataSource works. I have ViewModel like this
struct ViewModel: Hashable {
var id: Int
var value: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
I have tableView populated by cachedItems like ViewModele above. When API response arrives I want to add a new one, delete missing one, refresh viewModel.value of items already present in tableView and finally order it. Everything works fine except one thing - reloading items.
My understanding of DiffableDataSource was that it compares item.hash() to detect if the item is already present and if so then if cachedItem != apiItem, it should reload. Unfortunately, this is not working and snapshot does delete & insert instead of reloading.
Is DiffableDataSource supposed to do that?
Of course, I have a solution - to make it work I need to iterate through cachedItems, when new items contains the same id, I update cachedItem, then I applySnapshot without animation and after then i finally can applySnapshot with animation for deleting/inserting/ordering animation.
But this solution seems to be more like a hack than a valid code. Is there a cleaner way how to achieve this?
UPDATE:
There is the code showing the problem. It should work in playground. For example. items and newItems containt viewModel with id == 0. Hash is the same so diffableDataSource should just reload because subtitle is different. But there is visible deletion / inserting instead reload
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
let tableView = UITableView()
var diffableDataSource: UITableViewDiffableDataSource<Section, ViewModel>?
enum SelectesItems {
case items
case newItems
}
var selectedItems: SelectesItems = .items
let items: [ViewModel] = [ViewModel(id: 0, title: "Title1", subtitle: "Subtitle2"),
ViewModel(id: 1, title: "Title2", subtitle: "Subtitle2"),
ViewModel(id: 2, title: "Title3", subtitle: "Subtitle3"),
ViewModel(id: 3, title: "Title4", subtitle: "Subtitle4"),
ViewModel(id: 4, title: "Title5", subtitle: "Subtitle5")]
let newItems: [ViewModel] = [ViewModel(id: 0, title: "Title1", subtitle: "New Subtitle2"),
ViewModel(id: 2, title: "New Title 2", subtitle: "Subtitle3"),
ViewModel(id: 3, title: "Title4", subtitle: "Subtitle4"),
ViewModel(id: 4, title: "Title5", subtitle: "Subtitle5"),
ViewModel(id: 5, title: "Title6", subtitle: "Subtitle6")]
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "CellID")
diffableDataSource = UITableViewDiffableDataSource<Section, ViewModel>(tableView: tableView, cellProvider: { (tableView, indexPath, viewModel) -> UITableViewCell? in
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "CellID")
cell.textLabel?.text = viewModel.title
cell.detailTextLabel?.text = viewModel.subtitle
return cell
})
applySnapshot(models: items)
let tgr = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tgr)
}
@objc func handleTap() {
switch selectedItems {
case .items:
applySnapshot(models: items)
selectedItems = .newItems
case .newItems:
applySnapshot(models: newItems)
selectedItems = .items
}
}
func applySnapshot(models: [ViewModel]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, ViewModel>()
snapshot.appendSections([.main])
snapshot.appendItems(models, toSection: .main)
diffableDataSource?.apply(snapshot, animatingDifferences: true)
}
}
enum Section {
case main
}
struct ViewModel: Hashable {
let id: Int
let title: String
let subtitle: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()