0

I am trying to update a tableView to UITableViewDiffableDataSource but I am having trouble with being able to delete/move the rows. With some help from a previous question, I have subclassed the dataSource and added the tableview overrides there. The rows will edit but when I leave and come back to the view they go back to the way they were as my data models in my VC are not updating with the edits. Can anyone help with this?

extension NSDiffableDataSourceSnapshot {
mutating func deleteItemsAndSections(_ items : [ItemIdentifierType]) {
        self.deleteItems(items)
        let emptySection = self.sectionIdentifiers.filter {
            self.numberOfItems(inSection: $0) == 0
        }
        self.deleteSections(emptySection)
    }
}


fileprivate class ListDrillDownDataSource: UITableViewDiffableDataSource<String,   ListItem> {

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    guard editingStyle == .delete else { return }
        // Delete the row from the data source
        if let item = self.itemIdentifier(for: indexPath) {
            var snapshot = self.snapshot()
            snapshot.deleteItemsAndSections([item])
            self.apply(snapshot, animatingDifferences: true)
    }
}

override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    return true
}

}

Edit**

I have made some progress by adding a backingStore and I am closer to being able to update an existing cell. However, I ultimately just keep getting stuck in the same loop of errors.

I started by adding a backingStore property who's values are a custom ListItem class. The class is hashable with a UUID() as one of the properties.

class ListDrillDownTableViewController: UITableViewController {

fileprivate var dataSource: ListDrillDownDataSource!
fileprivate var currentSnapshot: ListDrillDownSnaphot?

var list: List = List(name: "Test List", listStyle: .preSetStyle[0], listItems: [], users: [])
var backingStore = [UUID: ListItem]()

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.navigationItem.leftBarButtonItem = self.editButtonItem
    self.dataSource = createDataSource()
    updateUI(animated: false)
    
    tableView.rowHeight = 65 
}

Then I created a method to create the dataSource giving the cell provider the UUID to look up the listItem. I also updated the UI with the initial snapshot populating the UUIDs and the backingStore.

 fileprivate func createDataSource() -> ListDrillDownDataSource {
    let dataSource = ListDrillDownDataSource(tableView: tableView) { (tableView, indexPath, uuid) -> UITableViewCell? in
        let cell = tableView.dequeueReusableCell(withIdentifier: "ListItemCell", for: indexPath) as! ListItemCell
        guard let listItem = self.backingStore[uuid] else { return cell }
        cell.titleLabel.text = listItem.title
        
        if let cost = listItem.cost {
            cell.costLabel.isHidden = false
            cell.costLabel.text = (listItem.costString(cost: cost))
        } else {
            cell.costLabel.isHidden = true
        }
        if listItem.note == "" {
            cell.noteIcon.isHidden = true
        } else {
            cell.noteIcon.isHidden = false
        }
        
        if listItem.askToReschedule && !listItem.hasRepeatInterval {
            cell.repeatIcon.isHidden = false
            cell.repeatIcon.image = UIImage(systemName: "plus.bubble")
        } else if !listItem.askToReschedule && listItem.hasRepeatInterval {
            cell.repeatIcon.isHidden = false
            cell.repeatIcon.image = UIImage(systemName: "repeat")
        } else {
            cell.repeatIcon.isHidden = true
        }
        return cell
    }
    self.tableView.dataSource = dataSource
    return dataSource
}

func updateUI(animated: Bool = true) {
    var snapshot = ListDrillDownSnaphot()
    snapshot.appendSections(["main"])
    let uuids = self.list.listItems.map { _ in UUID() }
    snapshot.appendItems(uuids)
    for (uuid, listItem) in zip(uuids, list.listItems) {
        self.backingStore[uuid] = listItem
    }
    self.dataSource.apply(snapshot, animatingDifferences: animated)
}

Finally, when the user taps a cell, makes there edits and taps done the view unwinds back to this controller and I attempt to update the dataSource in the unwind method. As it stands the app will create the first new cell but when a second is attempted it reloads the first row twice and as I keep adding it doubles the existing rows. I'm guessing because I'm appending the whole list over and over, but I can't figure out how to access JUST the appended uuid.

When I tap into an existing cell and edit the info it works, but if I go in again to the row and come out the cell goes back to it's original state. If I keep trying it bounces back and forth between the original state and the updated, almost like it is bouncing back and forth between snapshots?

   @IBAction func unwindToListDrillDownTableView(_ unwindSegue: UIStoryboardSegue) {
// Verify the correct segue is being used.
    guard unwindSegue.identifier == "DoneAddEditListItemUnwind" else { return }
    let sourceViewController = unwindSegue.source as! ListItemDetailTableViewController
// Update the Lists categories with any changes.
   self.list.listStyle?.categories = sourceViewController.currentCategories
// Verify a ListItem was returned.
    if let listItem = sourceViewController.listItem {
    // Check if ListItem is existing.
        if let indexOfExistingListItem = list.listItems.firstIndex(of: listItem) {
        // If existing, update the ListItem and then the view.
            list.listItems[indexOfExistingListItem] = listItem
            let uuid = self.dataSource.itemIdentifier(for: IndexPath(row: indexOfExistingListItem, section: 0))
            let uuids = self.list.listItems.map { _ in UUID() }
            var snapshot = self.dataSource.snapshot()
            snapshot.reloadItems([uuid!])
            for (uuid, listItem) in zip(uuids, list.listItems) {
                self.backingStore[uuid] = listItem
            }
            self.dataSource.apply(snapshot, animatingDifferences: true)
        } else {
        // If new, add to listItems collection and update the view.
            list.listItems.append(listItem)
            if self.backingStore.keys.isEmpty {
                updateUI()
            } else {
                var snapshot = self.dataSource.snapshot()
                let uuids = self.list.listItems.map { _ in UUID() }
                
                snapshot.reloadSections(["main"])
                snapshot.appendItems(uuids)
                for (uuid, listItem) in zip(uuids, list.listItems) {
                    self.backingStore[uuid] = listItem
                }
                self.dataSource.apply(snapshot, animatingDifferences: true)
            }
        }
    }
}
rBalmer
  • 67
  • 9
  • "my data models in my VC" Under normal circumstances there shouldn't be any. The diffable data source, once initially configured, contains the data model. You _can_ also maintain a separate model, and there are good reasons for doing so (e.g. async updates from outside the app), but make sure you're not doing it unnecessarily. — That said, I have a two-part article that might help you: start at https://www.biteinteractive.com/diffable-data-sources-and-data-storage-part-1/ – matt Jun 08 '21 at 21:35
  • Also I believe I sent you a link that shows how to delete / move rows with a diffable data source. Did you look at it? Here it is again: https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch08p445deleteTableRowsDiffable/ch21p718sections/RootViewController.swift – matt Jun 08 '21 at 21:41
  • I did yes, I just didn't include the move row portions in my post. The code I did include came from your link so thank you very much for that. – rBalmer Jun 09 '21 at 16:58
  • The reason I am trying to do this is because once a cell is created (It's basically a list app) the cell information can be edited (ie - reminders, notes, etc). These updated once returned to the cell may also modify the layout of the cell, for instance hiding or showing a label or image that holds an optional property. I read your 2 (very well written) articles and they seem to answer this, particularly when they talk about creating a backingStore. Am I on the right track with that assessment? – rBalmer Jun 09 '21 at 17:40
  • Yes, I believe so. What the second article tries to show is that if this is the kind of cell where the user can edit the contents, you will probably need a backing store to capture those edits and feed back into the diffable d.s. – matt Jun 09 '21 at 19:49

0 Answers0