I have a very simple UICollectionView
that uses compositional layout to easily achieve dynamic cell heights. Unfortunately doing that seems to disable content prefetching using UICollectionViewDataSourcePrefetching
. In the following sample code, the collectionView(_:prefetchItemsAt:)
method is called only once, upon initial display of the collection view. No scrolling action leads to further calls to the method.
What can I do to get prefetching working?
class ViewController: UIViewController,
UICollectionViewDataSource,
UICollectionViewDelegate,
UICollectionViewDataSourcePrefetching
{
@IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.collectionViewLayout = createLayout()
collectionView.dataSource = self
collectionView.delegate = self
collectionView.prefetchDataSource = self
collectionView.register(MyCell.self, forCellWithReuseIdentifier: "cell")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
cell.label.text = String(repeating: "\(indexPath) ", count: indexPath.item)
return cell
}
// this is only called once. Why?
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
print("prefetch for \(indexPaths)")
}
private func createLayout() -> UICollectionViewLayout {
let layout = UICollectionViewCompositionalLayout { (_, _) -> NSCollectionLayoutSection? in
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)
group.interItemSpacing = .fixed(16)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
section.interGroupSpacing = 8
return section
}
return layout
}
}
class MyCell: UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
backgroundColor = .orange
contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
(Using Xcode 11.5 / iOS 13.5. The collectionView
outlet is connected to a full-screen instance in a storyboard)
EDIT: Further testing shows that heightDimension: .estimated(44)
seems to be the cause. Replacing this with .absolute(44)
makes prefetching work again, but that of course defeats the purpose of having such a layout with multi-line text. Seems like a bug, and I've filed FB7849272 if anyone would like to dupe it.
EDIT/2: For the time being, I can avoid this situation by using a plain old flow layout and calculate each individual cell height, this also makes prefetching work again. Nevertheless, I'm curious if there isn't a workaround while still using compositional layouts, so I added a bounty.