I'm having difficulty finding the use of NSDiffableDataSourceSnapshot reloadItems(_:)
:
If the item I ask to reload is not equatable to an item that is already present in the data source, I crash with:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempted to reload item identifier that does not exist in the snapshot: ProjectName.ClassName
But if the item is equatable to an item that is already present in the data source, then what's the point of "reloading" it?
You might think the answer to the second point is: well, there might be some other aspect of the item identifier object that is not part of its equatability but does reflect into the cell interface. But what I find is that that's not true; after calling reloadItems
, the table view does not reflect the change.
So when I want to change an item, what I end up doing with the snapshot is an insert
after the item to be replaced and then a delete
of the original item. There is no snapshot replace
method, which is what I was hoping reloadItems
would turn out to be.
(I did a Stack Overflow search on those terms and found very little — mostly just a couple of questions that puzzled over particular uses of reloadItems
, such as How to update a table cell using diffable UITableView. So I'm asking in a more generalized form, what practical use has anyone found for this method?)
Well, there's nothing like having a minimal reproducible example to play with, so here is one.
Make a plain vanilla iOS project with its template ViewController, and add this code to the ViewController.
I'll take it piece by piece. First, we have a struct that will serve as our item identifier. The UUID is the unique part, so equatability and hashability depend upon it alone:
struct UniBool : Hashable {
let uuid : UUID
var bool : Bool
// equatability and hashability agree, only the UUID matters
func hash(into hasher: inout Hasher) {
hasher.combine(uuid)
}
static func ==(lhs:Self, rhs:Self) -> Bool {
lhs.uuid == rhs.uuid
}
}
Next, the (fake) table view and the diffable data source:
let tableView = UITableView(frame: .zero, style: .plain)
var datasource : UITableViewDiffableDataSource<String,UniBool>!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.datasource = UITableViewDiffableDataSource<String,UniBool>(tableView: self.tableView) { tv, ip, isOn in
let cell = tv.dequeueReusableCell(withIdentifier: "cell", for: ip)
return cell
}
var snap = NSDiffableDataSourceSnapshot<String,UniBool>()
snap.appendSections(["Dummy"])
snap.appendItems([UniBool(uuid: UUID(), bool: true)])
self.datasource.apply(snap, animatingDifferences: false)
}
So there is just one UniBool in our diffable data source and its bool
is true
. So now set up a button to call this action method which tries to toggle the bool
value by using reloadItems
:
@IBAction func testReload() {
if let unibool = self.datasource.itemIdentifier(for: IndexPath(row: 0, section: 0)) {
var snap = self.datasource.snapshot()
var unibool = unibool
unibool.bool = !unibool.bool
snap.reloadItems([unibool]) // this is the key line I'm trying to test!
print("this object's isOn is", unibool.bool)
print("but looking right at the snapshot, isOn is", snap.itemIdentifiers[0].bool)
delay(0.3) {
self.datasource.apply(snap, animatingDifferences: false)
}
}
}
So here's the thing. I said to reloadItems
with an item whose UUID is a match, but whose bool
is toggled: "this object's isON is false". But when I ask the snapshot, okay, what have you got? it tells me that its sole item identifier's bool
is still true.
And that is what I'm asking about. If the snapshot is not going to pick up the new value of bool
, what is reloadItems
for in the first place?
Obviously I could just substitute a different UniBool, i.e. one with a different UUID. But then I cannot call reloadItems
; we crash because that UniBool is not already in the data. I can work around that by calling insert
followed by remove
, and that is exactly how I do work around it.
But my question is: so what is reloadItems
for, if not for this very thing?
cellForRowAt
implementation. – Vigbool
value, then why am I including it at all? You're saying I would keep the bools only in the external "backing store"? That seems nutty to me. – VigUniBool
a class and not a struct then you get the expected behaviour. It seems like` reloadItems` does not actually take the new value from the snapshot so it works with a reference type. – FongreloadItems
in the first place, so that doesn't really prove as much as one might have hoped. I expect I'll be told that you are supposed to use a back store and that this "works as intended". But it's worth a try. Thanks for confirming my intuitions about it. – VigreconfigureItems
method which seems to have the same problem asreloadItems
where I had to change my model from struct to class in order to get it to work. The doc says thatreconfigureItems
should be used to update the contents of existing cells without replacing them with new cells but the only difference I spotted so far is thatreloadItems
triggersprepareForReuse
of the cell whilereconfigureItems
does not. – Twospot