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

https://github.com/yccheok/ios-tutorial/tree/7a6dafbcc8c61dd525bd82ae10c6a3bd67538b7f
Recently, we plan to migrate to DiffableDataSource
Our models are pretty straightforward
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 {
}
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
}
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)
}
// 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.

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?
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With