Reputation: 2364
I have a collectionView with a header designed in a .xib file. It has a simple label and it's text supports dynamicType.
How can I set the height of that header to be dynamic based on that label and the auto layout constraints in Storyboard?
So far, I've got this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let kind = UICollectionView.elementKindSectionHeader
let indexPath = IndexPath(row: 0, section: section)
if let headerView = collectionView.supplementaryView(forElementKind: kind, at: indexPath) as? SectionHeaderView {
headerView.layoutIfNeeded()
headerView.setNeedsLayout()
let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
return size
}
return CGSize(width: 0, height: 0)
}
But it does not show any header.
SectionHeaderView.xib looks like this:
CollectionView looks like this: you see 3 sections, but you don't see a header.
What can I do to let AutoLayout determine the correct hight of the header?
Upvotes: 2
Views: 2871
Reputation: 1593
Assuming you've got the proper constraints setup for your header, and you return a dynamically deduced height in your referenceSizeForHeaderInSection
:
Observe UIContentSizeCategory.didChangeNotification
in your class:
NotificationCenter.default.addObserver(self, selector: #selector(fontSizeChanged), name: UIContentSizeCategory.didChangeNotification, object: nil)
Use your fontSizeChanged
function for updation:
@objc private func fontSizeChanged(_ sender: Any) {
//If you're dealing with minimal data, you can simply 'reloadData()'
//If not, I'm sure there are other efficient ways to make this work. I'm just exposing the provision
self.collectionView.reloadData()
}
Oh, and make sure you:
deinit {
NotificationCenter.default.removeObserver(self)
}
Upvotes: -1
Reputation: 5671
Use a custom flow layout to perfectly manage the height of a header in a collection view with the Dynamic Type
feature.
A header element is seen as a supplementary element for a collection view and the referenceSizeForHeaderInSection
'method' is only used for initialization: it's not called with the Dynamic Type
feature. 🤯
The solution hereafter is based on the layoutAttributesForElements
method of the custom layout that will be able to adapt the header height thanks to the UIFontMetrics
scaledValue
.
All that is fired by the invalidateLayout
method called in the traitCollectionDidChange
triggered when the user changes the font size. 🤓
STEP 1 ⟹ create a simple custom header class as follows for instance:
class MyHeaderClass: UICollectionReusableView {
override init(frame: CGRect) { super.init(frame: frame) }
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
}
STEP 2 ⟹ create a new empty .xib adding a reusable view and name it the exact same name as the class it refers to: don't forget to change its class name in the Identity Inspector
.
STEP 3 ⟹ register the .xib file in the controller:
collectionView.register(UINib(nibName: collectionViewHeaderFooterReuseIdentifier bundle: nil),
forSupplementaryViewOfKind: UICollectionElementKindSectionHeader,
withReuseIdentifier:collectionViewHeaderFooterReuseIdentifier)
STEP 4 ⟹ support this new cell in your data source (a header is a supplementary element for a collection view):
func collectionView(_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath) -> UICollectionReusableView {
if (kind == UICollectionView.elementKindSectionHeader) {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
withReuseIdentifier: collectionViewHeaderReuseIdentifier,
for: indexPath) as! MyHeader
headerView.myLabel.text = "Your Header Title"
return headerView
} else {
return UICollectionReusableView(frame: CGRect.null) }
}
... and in your delegate (this intializes the header size and makes it appear):
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: headerHeight)
}
.. after adding a global var headerHeight: CGFloat = 90.0
for initialization.
STEP 5 ⟹ create the custom flow layout to adapt the header height to the new font size:
class FlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)
layoutAttributes?.forEach({ (attribute) in
if (attribute.representedElementKind == UICollectionView.elementKindSectionHeader) {
headerHeight = UIFontMetrics.default.scaledValue(for: 22.0)
attribute.frame.size.height = headerHeight
}
})
return layoutAttributes
}
}
Don't forget to update the storyboard in Interface Builder
:
STEP 6 ⟹ inform the controller to trigger a layout update when the user changes the font size:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if (previousTraitCollection?.preferredContentSizeCategory != traitCollection.preferredContentSizeCategory) {
collectionView?.collectionViewLayout.invalidateLayout()
}
}
Following this rationale, the correct height of the headers is automatically set up according to the headers font size ⟹ I suggest to use the Xcode 11 new feature to test the Dynamic Type
very quickly. 👍
Upvotes: 0