How to update footer in Section via DiffableDataSource without causing flickering effect?

2.3k views Asked by At

A Section may contain 1 header, many content items and 1 footer.

For DiffableDataSource, most of the online examples, are using enum to represent Section. For instance

func applySnapshot(_ animatingDifferences: Bool) {
    var snapshot = Snapshot()
    
    snapshot.appendSections([.MainAsEnum])

    snapshot.appendItems(filteredTabInfos, toSection: .MainAsEnum)

    dataSource?.apply(snapshot, animatingDifferences: animatingDifferences)
}

However, when the Section has a dynamic content footer, we may need to use struct to represent Section. For instance

import Foundation

struct TabInfoSection {

    // Do not include content items [TabInfo] as member of Section. If not, any mutable 
    // operation performed on content items, will misguide Diff framework to throw 
    // away entire current Section, and replace it with new Section. This causes 
    // flickering effect.

    var footer: String
}

extension TabInfoSection: Hashable {
}

But, how are we suppose to update only footer?

The current approach provided by

DiffableDataSource: Snapshot Doesn't reload Headers & footers is not entirely accurate

If I try to update footer

class TabInfoSettingsController: UIViewController {
    …

    func applySnapshot(_ animatingDifferences: Bool) {
        var snapshot = Snapshot()

        let section = tabInfoSection;
        
        snapshot.appendSections([section])

        snapshot.appendItems(filteredTabInfos, toSection: section)

        dataSource?.apply(snapshot, animatingDifferences: animatingDifferences)
    }

var footerValue = 100

extension TabInfoSettingsController: TabInfoSettingsItemCellDelegate {
    func crossButtonClick(_ sender: UIButton) {
        let hitPoint = (sender as AnyObject).convert(CGPoint.zero, to: collectionView)
        if let indexPath = collectionView.indexPathForItem(at: hitPoint) {
            // use indexPath to get needed data

            footerValue = footerValue + 1
            tabInfoSection.footer = String(footerValue)
            
            //
            // Perform UI updating.
            //
            applySnapshot(true)
        }
    }
}

I will get the following flickering outcome.

enter image description here

The reason of flickering is that, the diff framework is throwing entire old Section, and replace it with new Section, as it discover there is change in TabInfoSection object.

Is there a good way, to update footer in Section via DiffableDataSource without causing flickering effect?

p/s The entire project source code can be found in https://github.com/yccheok/ios-tutorial/tree/broken-demo-for-footer-updating under folder TabDemo.

3

There are 3 answers

0
Jay On BEST ANSWER

Have you thought about making a section only for the footer? So that way there's no reload, when it flickers, since it's technically not apart of the problematic section?

3
Dhawal On

if you want to update only collectionview footer text then make it variable of TabInfoSettingsFooterCell.

var tableSection: TabInfoSettingsFooterCell?

DataSource

func makeDataSource() -> DataSource {
    let dataSource = DataSource(
        collectionView: collectionView,
        cellProvider: { (collectionView, indexPath, tabInfo) -> UICollectionViewCell? in
            guard let tabInfoSettingsItemCell = collectionView.dequeueReusableCell(
                withReuseIdentifier: TabInfoSettingsController.tabInfoSettingsItemCellClassName,
                for: indexPath) as? TabInfoSettingsItemCell else {
                return nil
            }
            
            tabInfoSettingsItemCell.delegate = self
            tabInfoSettingsItemCell.reorderDelegate = self
            tabInfoSettingsItemCell.textField.text = tabInfo.getPageTitle()
            
            return tabInfoSettingsItemCell
        }
    )
    
    dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
        guard kind == UICollectionView.elementKindSectionFooter else {
            return nil
        }
    
        let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
        
        guard let tabInfoSettingsFooterCell = collectionView.dequeueReusableSupplementaryView(
            ofKind: kind,
            withReuseIdentifier: TabInfoSettingsController.tabInfoSettingsFooterCellClassName,
            for: indexPath) as? TabInfoSettingsFooterCell else {
            
            return nil
        }
        
        tabInfoSettingsFooterCell.label.text = section.footer

        //set tableSection value
        self.tableSection = tabInfoSettingsFooterCell

        return tabInfoSettingsFooterCell
    }
    
    return dataSource
}

TabInfoSettingsItemCellDelegate

func crossButtonClick(_ sender: UIButton) {
    let hitPoint = (sender as AnyObject).convert(CGPoint.zero, to: collectionView)
    if let indexPath = collectionView.indexPathForItem(at: hitPoint) {

        footerValue = footerValue + 1
        tabInfoSection.footer = String(footerValue)

        //Update section value
        self.tableSection?.label.text = String(footerValue)
    }
}
2
Diego Jiménez On

There is a fast fix for it, but you will loose the animation of the tableview. In TabInfoSettingsController.swift you can force false the animations in this function:

func applySnapshot(_ animatingDifferences: Bool) {
    var snapshot = Snapshot()
    let section = tabInfoSection;
    snapshot.appendSections([section])
    snapshot.appendItems(filteredTabInfos, toSection: section)
    dataSource?.apply(snapshot, animatingDifferences: false)
}

You will not see the flickering effect but you will loose the standard animation.