So I'm having a screen with a Diffable DataSource List CollectionView that will be reloaded when a ManagedContext will be changed.
My problem with the reload right now is that cells keep on randomly changing and most of the times they duplicate themselves with items evem from different sections.
What would be a good solution to reload this?
class MultiSectionExpandableListView: UIView {
weak var delegate: MultiSectionExpandableListViewDelegate?
private var objects: [HeaderItem]
private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<HeaderItem, ItemType>!
private lazy var noEntriesView: NoEntriesView = {
let view = NoEntriesView.instantiateNib()
view.frame = collectionView.bounds
return view
}()
init(frame: CGRect, objects: [HeaderItem]) {
self.objects = objects
super.init(frame: frame)
setupCollectionView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupCollectionView() {
var layoutConfig = UICollectionLayoutListConfiguration(appearance: .sidebar)
layoutConfig.headerMode = .firstItemInSection
layoutConfig.showsSeparators = false
layoutConfig.backgroundColor = .clear
let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
collectionView = UICollectionView(frame: bounds, collectionViewLayout: listLayout)
collectionView.delegate = self
collectionView.backgroundColor = .clear
addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: topAnchor),
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor)])
guard objects[0].items.count > 0 || objects[1].items.count > 0 || objects[2].items.count > 0 || objects[3].items.count > 0 else {
collectionView.backgroundView = noEntriesView
return
}
let headerCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, HeaderItem> {
(cell, indexPath, headerItem) in
var content = cell.defaultContentConfiguration()
content.text = headerItem.title
cell.contentConfiguration = content
var headerDisclosureOption = UICellAccessory.OutlineDisclosureOptions(style: .header)
headerDisclosureOption.tintColor = AppStorage.tintColor.uiColor()
cell.accessories = [.outlineDisclosure(options:headerDisclosureOption)]
}
let itemCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, ListItem> {
(cell, indexPath, listItem) in
var configuration = cell.defaultContentConfiguration()
configuration.text = listItem.title
configuration.secondaryText = listItem.description
configuration.imageProperties.maximumSize = CGSize(width: 50, height: 50)
configuration.imageProperties.reservedLayoutSize = CGSize(width: 60, height: 60)
let spinner = UIActivityIndicatorView(style: .large)
spinner.color = AppStorage.tintColor.uiColor()
spinner.startAnimating()
cell.accessories = [.customView(configuration: UICellAccessory.CustomViewConfiguration(customView: spinner, placement: .leading(displayed: .always)))]
cell.contentConfiguration = configuration
DownloadImageService.shared.loadImage(stringUrl: listItem.imageURL) { (data, error) in
guard error == nil, let data = data else { return }
configuration.image = UIImage(data: data)
cell.accessories = []
cell.contentConfiguration = configuration
}
}
dataSource = UICollectionViewDiffableDataSource<HeaderItem, ItemType>(collectionView: collectionView) {
(collectionView, indexPath, itemType) -> UICollectionViewCell? in
switch itemType {
case .header(let headerItem):
let cell = collectionView.dequeueConfiguredReusableCell(using: headerCellRegistration, for: indexPath, item: headerItem)
return cell
case .cell(let listItem):
let cell = collectionView.dequeueConfiguredReusableCell(using: itemCellRegistration, for: indexPath, item: listItem)
cell.backgroundConfiguration?.backgroundColor = .clear
return cell
}
}
var currentSnapshot = NSDiffableDataSourceSnapshot<HeaderItem, ItemType>()
currentSnapshot.appendSections(objects)
dataSource.apply(currentSnapshot)
for headerItem in objects {
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<ItemType>()
let headerListItem = ItemType.header(headerItem)
sectionSnapshot.append([headerListItem])
let listItemArray = headerItem.items.map { ItemType.cell($0) }
sectionSnapshot.append(listItemArray, to: headerListItem)
sectionSnapshot.expand([headerListItem])
dataSource.apply(sectionSnapshot, to: headerItem, animatingDifferences: false)
}
}
func reloadCollectionView(with newObjects: [HeaderItem]) {
objects = newObjects
guard objects[0].items.count > 0 || objects[1].items.count > 0 || objects[2].items.count > 0 || objects[3].items.count > 0 else {
collectionView.backgroundView = noEntriesView
return
}
var currentSnapshot = dataSource.snapshot()
currentSnapshot = NSDiffableDataSourceSnapshot<HeaderItem, ItemType>()
currentSnapshot.appendSections(objects)
dataSource.apply(currentSnapshot)
for headerItem in objects {
var sectionSnapshot = NSDiffableDataSourceSectionSnapshot<ItemType>()
let headerListItem = ItemType.header(headerItem)
sectionSnapshot.append([headerListItem])
let listItemArray = headerItem.items.map { ItemType.cell($0) }
sectionSnapshot.append(listItemArray, to: headerListItem)
sectionSnapshot.expand([headerListItem])
dataSource.apply(sectionSnapshot, to: headerItem, animatingDifferences: false)
}
}
}
And also here are my objects:
struct ListItem: Hashable {
let title: String
let description: String
let imageURL: String
}
struct HeaderItem: Hashable {
let title: String
let items: [ListItem]
}
enum ItemType: Hashable {
case header(HeaderItem)
case cell(ListItem)
}