3

Previously, I have a pretty straightforward code, which perform delete operation for UICollectionView.

// Remove from single source of truth.
viewController?.deleteTabInfo(indexPath)

//
// Perform UI updating.
//
self.collectionView.deleteItems(at: [indexPath])

It works pretty well

enter image description here

Complete source code (TabDemo with workable delete animation)

https://github.com/yccheok/ios-tutorial/tree/7a6dafbcc8c61dd525bd82ae10c6a3bd67538b7f


Recently, we plan to migrate to DiffableDataSource

Our models are pretty straightforward

Model

struct TabInfo {
    let id: Int64
    let type: TabInfoType
    var name: String?
    var colorIndex: Int
}

extension TabInfo: Hashable {
}

struct TabInfoSection {
    var tabInfos: [TabInfo]
    var footer: String
}

extension TabInfoSection: Hashable {
}

Data source

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
            }
            
            // This is used to handle delete button click event.
            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
        
        return tabInfoSettingsFooterCell
    }
    
    return dataSource
}

Snapshot

var filteredSection: TabInfoSection {
    guard let viewController = self.viewController else {
        return TabInfoSection(tabInfos: [], footer: "")
    }
    
    return TabInfoSection(
        tabInfos: viewController.tabInfos.filter({ $0.type != TabInfoType.Settings }),
        footer: "This is footer"
    )
}

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

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

    snapshot.appendItems(section.tabInfos, toSection: section)

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

Delete operation

// Remove from single source of truth.
viewController?.deleteTabInfo(indexPath)

//
// Perform UI updating.
//
applySnapshot(true)

However, the outcome is not promising. Instead of delete animation, it is showing flickering effect.

enter image description here

Complete source code (TabDemo with flickering effect during deletion)

https://github.com/yccheok/ios-tutorial/tree/c26ce159472fe2d25d181f9835ef11f1081b0bbc


Do you have idea what steps we have missed out, which causes delete animation doesn't work properly?

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

1 Answers1

2

Currently, instead of using enum, we are using struct to represent Section.

The reason is that, we have a dynamic content footer. Using struct enables us to carry the dynamic content information for the footer.

Our initial Section class looks as following

import Foundation

struct TabInfoSection {
    var tabInfos: [TabInfo]
    var footer: String
}

extension TabInfoSection: Hashable {
}

However, this is a mistake. As, we include content items TabInfo as member of Section.

When there is any mutable operation performed on content items, this is causing Diff framework to throw away entire current Section, and replace it with new Section. (Because Diff framework detects there is change in Section).

This causes flickering effect.

The correct implementation should be

import Foundation

struct TabInfoSection {
    var footer: String
}

extension TabInfoSection: Hashable {
}

P/s But, this is causing additional problem, when we want to update footer explicitly. I describe it in another question - How to update footer in Section via DiffableDataSource without causing flickering effect?

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875