From what I concluded, a diffable data source is designed to insert/delete/update/move only the differences from the new snapshot (if animatingDifferences is true), for those irrelevent cells, they should not be reloaded.
But whenever I apply a snapshot, even if the new snapshot is exactly the current one from the data source, almost every on-screen cells got reloaded. Here's a simple test view controller:
swift
import UIKit
class CollectionViewController: UIViewController {
enum Section: Hashable {
case main
}
struct Item: Hashable {
let id: String
}
private lazy var dataSource: UICollectionViewDiffableDataSourceSection, Item = {
return UICollectionViewDiffableDataSourceSection, Item(collectionView: collectionView) { collectionView, indexPath, item in
print("data source reloading cell", item, indexPath)
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "cell",
for: indexPath
) as! Cell
cell.item = item
return cell
}
}()
private lazy var listLayout: UICollectionViewCompositionalLayout = {
let conf = UICollectionLayoutListConfiguration(appearance: .plain)
return UICollectionViewCompositionalLayout.list(using: conf)
}()
private lazy var regularLayout: UICollectionViewCompositionalLayout = {
let item = NSCollectionLayoutItem(
layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50))
)
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(50)),
subitem: item,
count: 1
)
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}()
private lazy var collectionView: UICollectionView = {
let view = UICollectionView(frame: .zero, collectionViewLayout: regularLayout)
view.register(Cell.self, forCellWithReuseIdentifier: "cell")
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemBackground
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
var snapshot = NSDiffableDataSourceSnapshotSection, Item()
snapshot.appendSections([.main])
snapshot.appendItems((0 .. 100).map { .init(id: "\($0)") })
dataSource.apply(snapshot, animatingDifferences: false)
Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { _ in
print("\n\nDelayed Reloading")
var snapshot = self.dataSource.snapshot()
self.dataSource.apply(snapshot, animatingDifferences: true)
}
}
class Cell: UICollectionViewCell {
var item: Item? {
didSet {
label.text = item?.id
}
}
private(set) lazy var label: UILabel = {
let view = UILabel()
view.font = .systemFont(ofSize: 48, weight: .semibold)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
print("cell inited")
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
label.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
Output
Xcode 12.5
iPhone 11 device (iOS 14.5.1)
Delayed Reloading
data source reloading cell Item(id: "2") [0, 2]
data source reloading cell Item(id: "4") [0, 4]
data source reloading cell Item(id: "6") [0, 6]
data source reloading cell Item(id: "8") [0, 8]
data source reloading cell Item(id: "10") [0, 10]
data source reloading cell Item(id: "12") [0, 12]
data source reloading cell Item(id: "14") [0, 14]
data source reloading cell Item(id: "1") [0, 1]
data source reloading cell Item(id: "3") [0, 3]
data source reloading cell Item(id: "5") [0, 5]
data source reloading cell Item(id: "7") [0, 7]
data source reloading cell Item(id: "9") [0, 9]
data source reloading cell Item(id: "11") [0, 11]
data source reloading cell Item(id: "13") [0, 13]
data source reloading cell Item(id: "15") [0, 15]
data source reloading cell Item(id: "0") [0, 0]
data source reloading cell Item(id: "11") [0, 11]
Is this an expected behavior?
I've also run the same test on UITableView, which is much more reasonable. Only some off-screen cells are reloaded.
Post
Replies
Boosts
Views
Activity
For UITableViewCells and UICollectionViewListCells, their contentView.layoutMarginsGuide respect the colletionView's layoutMarginsGuide automatically, which can be 16 or 20 points horizontally.
This is the expected behavior.
But for regular UICollectionViewCells with a regular compositional layout, their contentView.layoutMarginsGuide are defaulted to 8 points.
I don't see where I can hook into and change the layout margins of the cells . I've looked into contentInset property and contentInsetReference property of section and item when constructing a compositional layout, but with no luck, their intentions are to change the width of the cells themselves.