Moving/Deleting Diffable TableView Rows

1.3k views Asked by At

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)
            }
        }
    }
}
0

There are 0 answers