显而易见,UICollectionViewCompositionalLayout 给我们带来易用性的同时也牺牲了 UICollectionViewLayout 的灵活性。当我们决定选择前者的时候,内心多少会祈祷着在需求范围之内不要有什么坑啊拜托!然而,Apple 不遂人意,问就是解决提出问题的人可解千愁~
玩笑终归是玩笑,该搬的🧱一块也少不了……
如“标题”的问题描述
默认情况下,当 NSCollectionLayoutDecorationItem 用作背景时,会包含 NSCollectionLayoutBoundarySupplementaryItem。如果要设置内容的偏移,只能通过 contentInsets 进行 Hard Code。对于固定高度的 Supplementary 自然无压力,但对于自适应宽高的情况就显得力有未逮了。
名为 Re-Layout 的小聪明
在 NSCollectionLayoutSection 中有一个叫做 visibleItemsInvalidationHandler 的回调会告知开发者当前可视 Item 被展示前的准确信息:
A closure called before each layout cycle to allow modification of the items in the section immediately before they’re displayed.
但是其允许修改的属性很有限,我们需要涉及到宽高就不在其中。不过借助其计算出来的 frame 等信息,我们可以在适当的时机进行重新布局,变相完成我们的需求。以下贴出核心代码逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| UICollectionViewCompositionalLayout { sectionIdx, env in let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(150)) let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: 3) let result = NSCollectionLayoutSection(group: group) result.contentInsets = .init(top: 14, leading: 34, bottom: 14, trailing: 34) let decorationItem = NSCollectionLayoutDecorationItem.background(elementKind: String(describing: Decoration.self)) decorationItem.contentInsets = .init(top: self._headerHeight ?? 0, leading: 20, bottom: 0, trailing: 20)
result.decorationItems = [decorationItem] let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(20)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .top) result.boundarySupplementaryItems = [header] result.visibleItemsInvalidationHandler = { items, offset, env in if let decorationItem = items.first(where: { $0.representedElementCategory == .decorationView }), let header = items.first(where: { $0.representedElementCategory == .supplementaryView }), decorationItem.frame.origin.y == header.frame.origin.y { self._headerHeight = header.frame.height DispatchQueue.main.async { self._collectionView.collectionViewLayout.invalidateLayout() } } } return result }
|
总结
早在两年前,这里就提出了相关问题,只是一直没有被有效地解答。希望这个“小聪明”能够解决这个问题,更进一步则是期盼 Apple 早日对此做出 API 级别的支持,🙏