Created
November 9, 2023 20:51
-
-
Save kylebshr/0731820412aa60f9d0a85a9f0f8ac610 to your computer and use it in GitHub Desktop.
UICollectionViewCompositionalLayout subclass that can stack a pinned collection-level header with pinned section-level headers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class StackedHeaderCompositionalLayout: UICollectionViewCompositionalLayout { | |
let collectionHeaderKind: String | |
let sectionHeaderKind: String | |
init( | |
section: NSCollectionLayoutSection, | |
configuration: UICollectionViewCompositionalLayoutConfiguration, | |
collectionHeaderKind: String, | |
sectionHeaderKind: String | |
) { | |
self.collectionHeaderKind = collectionHeaderKind | |
self.sectionHeaderKind = sectionHeaderKind | |
super.init(section: section, configuration: configuration) | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { | |
let layoutAttributes = super.layoutAttributesForElements(in: rect) | |
let collectionHeaderAttributes = layoutAttributes?.first(where: { $0.representedElementKind == collectionHeaderKind }) | |
collectionHeaderAttributes?.zIndex = 100 | |
let collectionHeaderFrame = collectionHeaderAttributes?.frame ?? .zero | |
layoutAttributes?.forEach { attributes in | |
guard attributes.representedElementKind == sectionHeaderKind else { | |
return | |
} | |
attributes.zIndex = 50 | |
// The positition of the header to pin it to the top, while allowing it to scroll down. | |
let pinnedTop = max(collectionHeaderFrame.maxY, attributes.frame.origin.y) | |
let section = attributes.indexPath.section | |
let nextSection = IndexPath(row: 0, section: section + 1) | |
if let nextAttributes = layoutAttributesForSupplementaryView(ofKind: sectionHeaderKind, at: nextSection) { | |
let nextHeaderTop = nextAttributes.frame.origin.y | |
let offsetPinnedTop = min(pinnedTop, nextHeaderTop - attributes.frame.height) | |
attributes.frame.origin.y = offsetPinnedTop | |
} else { | |
attributes.frame.origin.y = pinnedTop | |
} | |
} | |
return layoutAttributes | |
} | |
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { | |
true | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment