Since UICollectionViewCompositionalLayout released, I've been able to create dynamic-height UICollectionViewCells without issue using AutoLayout. However, I noticed today that it seems this no longer works.
I have tried all combinations of setNeedsLayout, layoutIfNeeded, changing the autoresizing mask on the cell, header, collectionView, labels... nothing changes. However, it DOES correctly size the header/cell once they are reused. If you run the example code below, scroll down until they are off-screen, and they will be correctly re-sized when they reappear (which is how they used to appear on first load).
So what is wrong with the below code? Previously, this would correctly size the header and cell to fit the content (in this case, a multi-line label). Is there something obvious I'm missing? The code below is the same format I've been using for over a year without issue until now. All suggestions/advice welcome!
- macOS Ventura 13.3.1
- Xcode 14.3.1
- Running on iOS 16.4 simulator
- Issue also present in iOS 16.0 simulator
- Issue also present on physical device (iPhone 14 Pro Max, iOS 16.5.1)
ViewController:
class ViewController: UIViewController {
enum Section: Int, CaseIterable { case main }
private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Section, AnyHashable>!
override func viewDidLoad() {
super.viewDidLoad()
setupViewController()
setupCollectionView()
setupDataSource()
updateData()
}
private func updateData() {
var snapshot = NSDiffableDataSourceSnapshot<Section, AnyHashable>()
snapshot.appendSections(Section.allCases)
snapshot.appendItems(Array(0..<1), toSection: .main)
dataSource.apply(snapshot, animatingDifferences: true)
}
private func setupViewController() {
navigationItem.title = "Title"
view.backgroundColor = .systemGroupedBackground
}
}
extension ViewController: UICollectionViewDelegate {
private func setupCollectionView() {
collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeLayout())
collectionView.backgroundColor = .clear
collectionView.delegate = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
private func setupDataSource() {
let header = UICollectionView.SupplementaryRegistration<CustomHeader>(elementKind: UICollectionView.elementKindSectionHeader) { header, kind, indexPath in
header.set(headerText: "A really really long header title that is so long that it cannot fit on a single line, so it must span multiple lines.")
}
let cell = UICollectionView.CellRegistration<CustomCell, AnyHashable> { cell, indexPath, item in
cell.set(cellText: "A really really long chunk of text that is so long that it cannot fit on a single line, so it must span multiple lines.")
}
dataSource = UICollectionViewDiffableDataSource<Section, AnyHashable>(collectionView: collectionView) { collectionView, indexPath, item in
return collectionView.dequeueConfiguredReusableCell(using: cell, for: indexPath, item: item)
}
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
return collectionView.dequeueConfiguredReusableSupplementary(using: header, for: indexPath)
}
}
private func makeLayout() -> UICollectionViewLayout {
return UICollectionViewCompositionalLayout { sectionIndex, environment in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 10, trailing: 20)
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [header]
return section
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
}
Screenshot