5

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.

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • it show flickering effect because of animation when apply snapshot try like this: `applySnapshot(false)` – Dhawal Oct 13 '20 at 06:27
  • @Dhawal This doesn't solve the underlying problem, as we want to avoid unnecessary redrawing on the entire Section. We only want to redraw be done on footer. – Cheok Yan Cheng Oct 13 '20 at 14:13
  • It is due to incorrect understanding of types. Let's see on snapshot- `NSDiffableDataSourceSnapshot` - as it is obvious it expects `SectionIdentifierType` and operates with *section identifiers`, by changing `footer` it is changed section identifier, ie. it means throw away old section (having previous identifier) and add completely new section. It is not for *section model* it is only for section ID - you must not change ID of section, but have separate model and just call `snapshot.reloadSections([your_idnetifier])` – Asperi Oct 18 '20 at 09:06

3 Answers3

2

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?

Jay
  • 2,591
  • 1
  • 16
  • 28
1

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.

Diego Jiménez
  • 1,398
  • 1
  • 15
  • 26
  • 1
    This doesn't solve the underlying problem. It is just hiding the problem under the carpet. We want to avoid unnecessary redrawing on the entire Section. We only want to redraw be done on footer. – Cheok Yan Cheng Oct 15 '20 at 09:55
  • Sure, that's why I said 'fast fix' for flickering effect. And if you don't want to update the entire section you have to manipulate the UI directly as said @Dhawal in the reply before. – Diego Jiménez Oct 15 '20 at 10:09
0

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)
    }
}
Dhawal
  • 1,055
  • 6
  • 10