UITableViewDiffableDataSource woes: updating an existing item & applying first item

I have a UITableVIew with a UITableViewDiffableDataSource. The tableView also uses and custom Cell Class.

Adding Items essentially works. By tapping and holding a cell I go to a viewController where I can update/change several fields of my item. Here's the method used :

Code Block
   func save(_ annotation: Annotation, withMapImage mapImage: AnnotationsController.MapImage?) {
    //Save the project to update Last Edited
    projectsController.save(annotationsController.project, withImage: nil)
    //Get current snapshot
    var snapshot = dataSource.snapshot()
     
    //Replace updated annotation by deleting old version from snapshot first
    if snapshot.itemIdentifiers.contains(annotation) {
      snapshot.deleteItems([annotation])
    }
     
    snapshot.appendItems([annotation], toSection: annotation.type)
     
    //Apply the snapshot on the main thread
    DispatchQueue.main.async {
      self.dataSource.apply(snapshot, animatingDifferences: true)
      self.tableView.reloadData()
    }
     
    //Save to storage
    annotationsController.save(annotation, withMapImage: nil)
  }

Two options:
1) With all of the above code in place, the changes are reflected in the tableView, but ONLY when the
Code Block
self.tableView.reloadData()
is there! But this shouldn't be necessary with a diffable dataSource?

2) if I omit the if
Code Block
snapshot.itemIdentifiers.contains(annotation) { ...

the changes are also reflected immediately, but the console throws a warning:

[UIDiffableDataSource] Warning: 1 inserted identifier(s) already present; existing items will be moved into place for this current insertion. Please note this will impact performance if items are not unique when inserted.

TL;DR What's the correct way of updating an existing item when using a UITableViewDiffableDataSource?


ALSO, on a side note: when I insert the very first item into the DataSource/Snapshot I get a warning also

Code Block
TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. Table view: <UITableView: 0x7f9213826200; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000035ff780>; 

I thought that forcing the .apply() on the main thread should prevent that? Programmatic UI, no storyboards, I do add tableView as subview to the view!

Replies

Anyone ?
It looks like you are deleting and then inserting the same annotation. Since the annotation has the same identity, diffable data source will only update something if the position changes. If the annotation gets deleted from the back and then inserted again, nothing happens since the old and the new snapshot look exactly the same.

It looks like you may want to just reload an annotation? You can let diffable data source know that it is supposed to reload a cell even though the identity and its position hasn't changed by calling reloadItemsWithIdentifiers: on the snapshot.

The warning you are seeing about "UITableView was told to layout its visible cells and other contents without being in the view hierarchy" probably means you are calling apply before UITableView is in a window. The most common case where this happens is if you call apply from inside a view controllers viewDidLoad, where the view was loaded but is not yet in the view hierarchy.
Thank you! I'll try reloadItemsWithIdentifier

The warning you are seeing about "UITableView was told to layout its visible cells and other contents without being in the view hierarchy" probably means you are calling apply before UITableView is in a window. The most common case where this happens is if you call apply from inside a view controllers viewDidLoad, where the view was loaded but is not yet in the view hierarchy.

About the tableView issue. On first load I do call apply from viewDidLoad and got away with the warning by forcing it on the main queue:

Code Block      //Force the update on the main thread to silence a warning about tableview not being in the hierarchy!
    DispatchQueue.main.async {
      self.dataSource.apply(snapshot, animatingDifferences: true)
    }

Where would be more appropriate to call apply() ?

However, when saving or updating an annotation the. situation is different. The ViewController is already loaded and pushes another ViewController on top to add or edit and existing annotation. When the user taps save, a delegate method is called on the original ViewController (the save method in my post). That's when I get the warning. How do I prevent that warning in this case ?

To summarize: AnnotationsViewController > AnnotationDetailsViewController > Calls save() on AnnotationsViewController (delegate method)

Also, I don't use storyboards, all programmatic UI