Though I discovered a number of tutorials / questions on dynamically sizing cells UICollectionView
I used to be not in a position to obtain the identical for the header view.
TL;DR How you can autosize a UICollectionView
header? Fetching a mock header in collectionView:structure:referenceSizeForHeaderInSection
crashes in iOS 18 and utilizing preferredLayoutAttributesFitting
in UICollectionReusableView
subclass has no impact.
The purpose:
A UICollectionReusableView
subclass containing two UILabel
for title and content material:
+------------------+
| 20 |
|20 TitleLabel 20|
| 10 |
|20 ContentLabel 20|
| 20 |
+------------------+
The ContentLabel is configured to point out a number of traces. So, the header ought to auto-size based on the textual content in ContentLabel.
Answer earlier than iOS 18 crashes now
Till know I used the next code in SomeViewController
to dimension the header view to its content material:
func collectionView(_ collectionView: UICollectionView, structure collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection part: Int) -> CGSize {
// Question mock header to fetch its dimension
let indexPath = IndexPath(row: 0, part: part)
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: sort, withReuseIdentifier: HeaderId, for: indexPath)
// ...
return headerView.systemLayoutSizeFitting(CGSize(width: collectionView.body.width, top: UIView.layoutFittingCompressedSize.top), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
}
This labored advantageous till compiling the venture with Xcode 16 / iOS 18. Now the app crashes since it isn’t allowed to dequeue a view manually (mentioned right here)
preferredLayoutAttributesFitting
I discovered a number of sources, indicating that utilizing preferredLayoutAttributesFitting
inside the headerView subclass of UICollectionReusableView
ought to do the trick. Nonetheless, it doesn’t matter what I return right here, it has completely no impact.
Full instance
class SomeViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let CellId = "CellId"
let HeaderId = "HeaderId"
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
tremendous.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: CellId)
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderId)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection part: Int) -> Int {
return part == 0 ? 5 : 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId, for: indexPath)
cell.backgroundColor = indexPath.part == 0 ? .crimson : .blue
return cell
}
func collectionView(_ collectionView: UICollectionView, structure collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 50, top: 50)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind sort: String, at indexPath: IndexPath) -> UICollectionReusableView {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: sort, withReuseIdentifier: HeaderId, for: indexPath)
if let headerView = view as? HeaderView {
if indexPath.part == 0 {
headerView.configure(title: "Part 1", data: "That is part 1")
} else {
headerView.configure(title: "Part 2", data: "That is part 2 with an extended textual content. The peak of the header is mechanically adjusted to suit the textual content. And we make the textual content even longer to see the way it works. And we make the textual content even longer to see the way it works. And we make the textual content even longer to see the way it works.")
}
}
return view
}
func collectionView(_ collectionView: UICollectionView, structure collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection part: Int) -> CGSize {
// That is the one place which permits to alter the header top at runtime.
// Returning completely different sizes for various sections is not any drawback. Nonetheless,
// with out dequeuing a mock header and measuring its top, one can solely
// return an estimate.
//
// With out this technique all headers are created with the reference top given
// within the movement structure.
return CGSize(width: collectionView.body.width, top: 200)
}
}
class HeaderView: UICollectionReusableView {
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .headline)
label.textColor = .black
label.numberOfLines = 0
label.backgroundColor = .white
return label
}()
let infoLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .physique)
label.textColor = .black
label.numberOfLines = 0
label.backgroundColor = .white
return label
}()
// Initialisierung
override init(body: CGRect) {
tremendous.init(body: body)
setupView()
}
required init?(coder: NSCoder) {
tremendous.init(coder: coder)
setupView()
}
// Setup der View und Constraints
personal func setupView() {
backgroundColor = .inexperienced
addSubview(titleLabel)
addSubview(infoLabel)
NSLayoutConstraint.activate([
// Title-Label Constraints
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
// Info-Label Constraints
infoLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
infoLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
infoLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
infoLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20)
])
}
// Methoden zur Konfiguration der Labels
func configure(title: String, data: String) {
titleLabel.textual content = title
infoLabel.textual content = data
}
/*override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let autoLayoutAttributes = tremendous.preferredLayoutAttributesFitting(layoutAttributes)
// It doesn't matter what is returned right here, the consequence has no impact. So this
// instance returns a set worth as a substitute a calculated one utilizing
// systemLayoutSizeFitting
autoLayoutAttributes.body = CGRect(origin: autoLayoutAttributes.body.origin, dimension: CGSize(width: autoLayoutAttributes.body.width, top: 50))
return autoLayoutAttributes
}*/
}