cellProvider doesn't update after section removal when using UITableViewDiffableDataSource

132 Views Asked by At

I have some code which loads in some empty sections and items. Then if there is a network connection some calls are made for the visible cells. I have a button in each section which navigates to another view based on the section type, which uses the indexPath. My problem is that when I remove the fist section the cellProvider doesn't update the indexPath and button navigates to the wrong view. If I scroll however the problem goes away. How do I make sure the indexPath updates? EDIT: It appears that my headerViews are retaining their old indexPath, but the cellProvider correctly updates. The header views are set to the correct indexPath once you scroll away and come back.

private enum RowItemType: Hashable {
    case itemTypeOne(collection: MyCollection)
    case itemTypeTwo(collection: MyCollection)
    case itemTypeThree(collection: OtherCollection)
}

private enum SectionType: Hashable {
    case sectionOne
    case sectionTwo
    case sectionThree
}

class MyCollection: Codable, Hashable, Equatable {
    var identifier = UUID()
    var foos: [Foo]
    
    init(foos: [Foo] = [Foo]()) {
        self.foos = foos
    }
    
    // MARK: Equatable

    var hash: Int {
        var hasher = Hasher()
        hasher.combine(identifier)
        return hasher.finalize()
    }

    static func == (lhs: MyCollection, rhs: MyCollection) -> Bool {
        lhs.identifier == rhs.identifier
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
}

class OtherCollection: Codable, Hashable, Equatable {
    var identifier = UUID()
    var bars: [Bar]
    
    init(bars: [Bar] = [Bar]()) {
        self.bars = bars
    }
    
    // MARK: Equatable

    var hash: Int {
        var hasher = Hasher()
        hasher.combine(identifier)
        return hasher.finalize()
    }

    static func == (lhs: OtherCollection, rhs: OtherCollection) -> Bool {
        lhs.identifier == rhs.identifier
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
}

final class SomeTableViewController: UITableVIewController {
  ...code....
RELEVENT CODE
private func makeDataSource() -> UITableViewDiffableDataSource<SectionType, RowItemType> {
        
         UITableViewDiffableDataSource(tableView: tableView, cellProvider: { [weak self] tableView, indexPath, item in
            
            guard let self = self else {
                return UITableViewCell()
            }
            
            guard let marginTableView = tableView as? AutoMarginTableView else {
                return UITableViewCell()
            }
             
             switch item {
             case .itemTypeOne(collection: let collection):
                 let carousel = self.itemOneCarouCell(from: marginTableView, withSection: .sectionOne, atIndexPath: indexPath)
                 
                 if !collection.foos.isEmpty {
                     carousel.configureCell(with: .sectionOne, fooCollection: collection)
                     self.showViewAllButtonForHeader(collection.foos.count > 3, atIndexPath: indexPath)
                 } else {
                     carousel.configureCellForLoading()
                     self.getFoosTypeOne(collection: collection)
                 }
                 
                 return carousel
             case .itemTypeTwo(let collection):
                 let carousel = self.itemTwoCarouCell(from: marginTableView, withSection: .sectionTwo, atIndexPath: indexPath)
                 
                 if !collection.foos.isEmpty {
                     carousel.configureCell(with: .sectionTwo, fooCollection: collection)
                     self.showViewAllButtonForHeader(collection.foos.count > 3, atIndexPath: indexPath)
                 } else {
                     carousel.configureCellForLoading()
                     self.getFoosTypeTwo(collection: collection)
                 }
                 
                 return carousel
             case .ttemTypeThree(let collection):
                 let carousel = self.itemThreeCarouCell(from: marginTableView, withSection: .sectionThree, atIndexPath: indexPath)
                 if !collection.bars.isEmpty {
                     carousel.configureCell(with: collection)
                     self.showViewAllButtonForHeader(true, atIndexPath: indexPath)
                 } else {
                     carousel.configureCellForLoading()
                     self.getBars(collection: collection)
                 }
                 return carousel
          
        })
    }

      private func showViewAllButtonForHeader(_ show: Bool, atIndexPath indexPath: IndexPath) {
        if let headerView = self.tableView.headerView(forSection: indexPath.section) as? MyTableHeaderView {
            UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseInOut, animations: {
                headerView.showViewAll(show)
            }, completion: nil)
        }
    }

private func getFoosTypeOne(collection: MyCollection) {
        var snapshot = dataSource.snapshot()
        if FeatureFlags.isFeatureEnabled && snapshot.sectionIdentifiers.contains(.sectionOne) {
            
            someRepositry.getfoos { result in
                if case .success(let foos) = result {
                    if !foos.isEmpty {
                        collection.foos = foos
                        snapshot.reloadItems([.itemTypeOne(collection: collection)])
                    } else {
                        if snapshot.sectionIdentifiers.contains(.sectionOne) {
                            snapshot.deleteSections([.sectionOne])
                        }
                    }
                    
                    self.dataSource.apply(snapshot, animatingDifferences: true)
                }
            }
        }
    }

private func getFoosTypeTwo(collection: MyCollection) {
        var snapshot = dataSource.snapshot()
        if FeatureFlags.isFeatureEnabled && snapshot.sectionIdentifiers.contains(.sectionTwo) {
            
            someRepositry.getDifferentFoos { result in
                if case .success(let foos) = result {
                    if !foos.isEmpty {
                        collection.foos = foos
                        snapshot.reloadItems([.itemTypeTwo(collection: collection)])
                    } else {
                        if snapshot.sectionIdentifiers.contains(.sectionTwo) {
                            snapshot.deleteSections([.sectionTwo])
                        }
                    }
                    
                    self.dataSource.apply(snapshot, animatingDifferences: true)
                }
            }
        }
    }

private func getBars(collection: OtherCollection) {
        var snapshot = dataSource.snapshot()
        someService.getSomeCollection { someColl in
            DispatchQueue.main.async {
                if let someColl = someColl {
                    if !someColl.bars.isEmpty {
                        collection.bars = someColl.bars
                        snapshot.reloadItems([.itemTypeThree(collection: collection)])
                    } else {
                        if snapshot.sectionIdentifiers.contains(.sectionThree) {
                            snapshot.deleteSections([.sectionThree])
                        }
                    }
                }
                
                self.dataSource.apply(snapshot, animatingDifferences: true)
            }
            
        }
    }
}

extension SomeTabelViewController: SomeTableHeaderViewDelegate {
    func someTableHeaderViewTappedViewAll(_ headerView: SomeTableHeaderView) {
        guard headerView.section < dataSource.numberOfSections(in: tableView) else {
            return
        }
        
        let snapshot = dataSource.snapshot()
        let section = snapshot.sectionIdentifiers[headerView.section]
        switch section {
        case .sectionOne:
            if case .itemTypeOne(let collection) = snapshot.itemIdentifiers(inSection: .sectionOne).first, !collection.foos.isEmpty {
                let vc = SomeListViewController()
                navigationController?.pushViewController(vc, animated: true)
            }
        case .sectionTwo:
            if case .itemTypeTwo(let collection) = snapshot.itemIdentifiers(inSection: .sectionTwo).first, !collection.foos.isEmpty {
                let vc = SomeListViewController()
                navigationController?.pushViewController(vc, animated: true)
            }
        case .sectionThree:
            if case .itemTypeThree(let collection) = snapshot.itemIdentifiers(inSection: .sectionThree).first, !collection.bars.isEmpty {
                let vc = OtherViewController(collecton: collection)
                navigationController?.pushViewController(vc, animated: true)
            }
    }
}
0

There are 0 best solutions below