NSDiffableDataSourceSnapshot -> "deleteItems" does not work for cells that are collapsed

Hello.
Please help me to solve the issue:

NSDiffableDataSourceSnapshot -> "deleteItems" does not work for cells that are collapsed.

Also,

func sectionIdentifier(containingItem identifier: ItemIdentifierType) -> SectionIdentifierType?

does not return "SectionIdentifierType" if we pass in a cell that has been collapsed.

Please tell me, if I want to delete a cell in a section that is currently collapsed, how can I do this?

Thanks.
Can you show a complete code which can reproduce the same issue?
Pay attention to the method - removeCells()


Code Block language
class OutlineViewController: UIViewController {
enum Section {
case main
}
class OutlineItem: Hashable {
let title: String
let subitems: [OutlineItem]
let outlineViewController: UIViewController.Type?
init(title: String,
viewController: UIViewController.Type? = nil,
subitems: [OutlineItem] = []) {
self.title = title
self.subitems = subitems
self.outlineViewController = viewController
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: OutlineItem, rhs: OutlineItem) -> Bool {
return lhs.identifier == rhs.identifier
}
private let identifier = UUID()
}
var dataSource: UICollectionViewDiffableDataSource<Section, OutlineItem>! = nil
var outlineCollectionView: UICollectionView! = nil
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Modern Collection Views"
configureCollectionView()
configureDataSource()
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(removeCells))
}
@objc func removeCells() {
guard let item = menuItems.first(where: { $0.title == "Outlines" }) else {
return
}
let items = [item] + item.subitems
var snapshot = dataSource.snapshot()
snapshot.deleteItems(items)
dataSource.apply(snapshot, animatingDifferences: true)
}
private lazy var menuItems: [OutlineItem] = {
return [
OutlineItem(title: "Lists", subitems: [
OutlineItem(title: "Simple List", viewController: SimpleListViewController.self),
OutlineItem(title: "Reorderable List", viewController: ReorderableListViewController.self),
OutlineItem(title: "List Appearances", viewController: ListAppearancesViewController.self),
OutlineItem(title: "List with Custom Cells", viewController: CustomCellListViewController.self)
]),
OutlineItem(title: "Outlines", subitems: [
OutlineItem(title: "Emoji Explorer", viewController: EmojiExplorerViewController.self),
OutlineItem(title: "Emoji Explorer - List", viewController: EmojiExplorerListViewController.self)
]),
OutlineItem(title: "Cell Configurations", subitems: [
OutlineItem(title: "Custom Configurations", viewController: CustomConfigurationViewController.self)
])
]
}()
}
extension OutlineViewController {
func configureCollectionView() {
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: generateLayout())
view.addSubview(collectionView)
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .systemGroupedBackground
self.outlineCollectionView = collectionView
collectionView.delegate = self
}
func configureDataSource() {
let containerCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, OutlineItem> { (cell, indexPath, menuItem) in
var contentConfiguration = cell.defaultContentConfiguration()
contentConfiguration.text = menuItem.title
contentConfiguration.textProperties.font = .preferredFont(forTextStyle: .headline)
cell.contentConfiguration = contentConfiguration
let disclosureOptions = UICellAccessory.OutlineDisclosureOptions(style: .header)
cell.accessories = [.outlineDisclosure(options: disclosureOptions)]
cell.backgroundConfiguration = UIBackgroundConfiguration.clear()
}
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, OutlineItem> { cell, indexPath, menuItem in
var contentConfiguration = cell.defaultContentConfiguration()
contentConfiguration.text = menuItem.title
cell.contentConfiguration = contentConfiguration
cell.backgroundConfiguration = UIBackgroundConfiguration.clear()
}
dataSource = UICollectionViewDiffableDataSource<Section, OutlineItem>(collectionView: outlineCollectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, item: OutlineItem) -> UICollectionViewCell? in
if item.subitems.isEmpty {
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
} else {
return collectionView.dequeueConfiguredReusableCell(using: containerCellRegistration, for: indexPath, item: item)
}
}
let snapshot = initialSnapshot()
self.dataSource.apply(snapshot, to: .main, animatingDifferences: false)
}
func generateLayout() -> UICollectionViewLayout {
let listConfiguration = UICollectionLayoutListConfiguration(appearance: .sidebar)
let layout = UICollectionViewCompositionalLayout.list(using: listConfiguration)
return layout
}
func initialSnapshot() -> NSDiffableDataSourceSectionSnapshot<OutlineItem> {
var snapshot = NSDiffableDataSourceSectionSnapshot<OutlineItem>()
func addItems(_ menuItems: [OutlineItem], to parent: OutlineItem?) {
snapshot.append(menuItems, to: parent)
for menuItem in menuItems where !menuItem.subitems.isEmpty {
addItems(menuItem.subitems, to: menuItem)
}
}
addItems(menuItems, to: nil)
return snapshot
}
}

When you're using outline functionality (representing a hierarchy of items, including expand/collapse) with a diffable data source, you need to use NSDiffableDataSourceSectionSnapshot, which is a snapshot for a single section that is capable of representing parent-child relationships and expand/collapse state.

When you're using section snapshots, generally speaking you only want to use the top-level NSDiffableDataSourceSnapshot (for the entire data source) to initially populate the section identifiers, and then use the section snapshots afterwards to populate & update the items within each section.

The top-level NSDiffableDataSourceSnapshot doesn't support outline functionality, and doesn't know about parent-child relationships between items or expand/collapse state. (The top-level snapshot is essentially a "flattened" representation of all the data, containing only sections and expanded items.) Thus, you won't be able to query the top-level snapshot for information about items that are inside a collapsed parent, as those collapsed items don't exist in the top-level snapshot. Use the APIs on NSDiffableDataSourceSectionSnapshot to manage those items instead.

NSDiffableDataSourceSnapshot -&gt; "deleteItems" does not work for cells that are collapsed
 
 
Q