I wanted to implement a self-sizing horizontal UICollectionView with self-sizing cells. I used the observer on "contentSize" of collectionView, so that when I change Dynamic Type size, the collection view resizes together with cells. But when I change the number of items to 1. The single cell disappears!
The bug is reproducible when the estimatedItemSize = UICollectionViewFlowLayout.automaticSize, but if set some concrete value (e.g. CGSize(width: 100, height: 20)) everything works as expected. But I assume that automaticSize should have worked no matter how many cells need to be displayed.
Environment: Simulator iPhone 15 Pro, iOS 17.0.1, Xcode 15.0.1
I've created a bug report. Bug Number: FB13379594
// ViewController.swift
import UIKit
class ViewController: UIViewController {
var items: [String] = []
var horizontalInset: CGFloat = 10
var verticalInset: CGFloat = 5
@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = UICollectionViewFlowLayout.automaticSize // This causes One cell to disappear
// (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = CGSize(width: 100, height: 20) // This works
setOneItem()
// setMultipleItems()
collectionView.dataSource = self
let horizontalCollectionViewFlowLayout = UICollectionViewFlowLayout()
horizontalCollectionViewFlowLayout.scrollDirection = .horizontal
collectionView.collectionViewLayout = horizontalCollectionViewFlowLayout
let cellIdentifier = String(describing: HorizontalCollectionViewCell.self)
let nib = UINib(nibName: cellIdentifier, bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: cellIdentifier)
collectionView.contentInset = UIEdgeInsets(
top: verticalInset,
left: horizontalInset,
bottom: verticalInset,
right: horizontalInset
)
collectionView.horizontalScrollIndicatorInsets.left = 5
collectionView.horizontalScrollIndicatorInsets.right = 5
collectionView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
@IBAction func setOneItem(_ sender: UIButton) {
setOneItem()
collectionView.reloadData()
}
@IBAction func setMultipleItems(_ sender: UIButton) {
setMultipleItems()
collectionView.reloadData()
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if (object as? UICollectionView) == collectionView,
keyPath == "contentSize" {
setupCollectionViewHeight()
}
}
private func setOneItem() {
items = ["One"]
}
private func setMultipleItems() {
items = [
"One",
"Two Two",
"Three Three Three",
"Four Four Four Four",
"Five Five Five Five",
"Six Six Six Six Six Six"
]
}
private func setupCollectionViewHeight() {
if collectionView.contentSize.width != 0,
let layoutAttributes = firstItemLayoutAttributes() {
collectionViewHeightConstraint.constant = layoutAttributes.size.height + verticalInset * 2
}
}
private func firstItemLayoutAttributes() -> UICollectionViewLayoutAttributes? {
guard collectionView.numberOfSections > 0,
collectionView.numberOfItems(inSection: 0) > 0,
let layoutAttributes = collectionView.collectionViewLayout.layoutAttributesForItem(at: IndexPath(row: 0, section: 0)),
layoutAttributes.size.height > 0 else { return nil }
return layoutAttributes
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellIdentifier = String(describing: HorizontalCollectionViewCell.self)
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? HorizontalCollectionViewCell else {
return UICollectionViewCell()
}
let item = items[indexPath.row]
cell.titleLabel.text = item
return cell
}
}
// HorizontalCollectionViewCell.swift
import UIKit
class HorizontalCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
titleLabel.font = UIFont.preferredFont(forTextStyle: .body) // Dynamic Type Font
titleLabel.adjustsFontForContentSizeCategory = true // Automatically Adjusts Font
}
}