Paging Steve Breen! 😄
I've seen this question asked a zillion times but I've never seen an answer.
Is it possible to configure compositional layout to give you a grid of N columns (say 2 or 3) where each item in each row/group self-size their height, but the heights of those items are then set to be the height of the tallest item in their row.
This is easy to do if you ignore the self-sizing requirement (just use a fixed or absolute item height), but on the surface this doesn't even appear to be possible if you require self-sizing.
What I've Tried
-
Configuring a layout where the items are set to a fractional height of 1.0 and their group is set to an estimated height (ex: 100). I was hoping compositional layout would interpret this as, "Please self-size the height of the group and make each item 100% of that height." Unfortunately, compositional layout just uses the estimate you provide for the height as the actual height and no self-sizing occurs at all. Sad panda. 🐼
-
Use
visibleItemsInvalidationHandler
to attempt to identify which items share a row and reset their heights to be the height of the tallest item in that row. Sadly, the handler doesn't really have access to the data it needs to do this, nor is it allowed to change frames, nor is it called on the first layout pass, nor does it appear to be supported at all for layouts containing estimated items. In fact, if you try to use it with layouts that support self-sizing, you'll get this error message:
[UICompositionalLayout] note: the section invalidation handler cannot currently mutate visible items for layouts containing estimated items. Please file an enhancement request on UICollectionView.
- Wishing upon a star. Oh, and I also filed a radar asking: FB11776888
Here's a visual aid:
I have a little test program as well, but unfortunately I can't upload it here. Happy to share if it would be helpful. Here are a couple snippets:
#1
// This doesn't work
private func makeLayout1() -> UICollectionViewCompositionalLayout {
// Item
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// Group
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 2)
group.interItemSpacing = .fixed(10)
// Section
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
#2
// This self-sizes the heights of the items, but allows the items in each row to be different heights rather than providing any way to constrain them to the height of the tallest self-sized item in each row
private func makeLayout2() -> UICollectionViewCompositionalLayout {
// Item
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .estimated(100))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// Group
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(100))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, repeatingSubitem: item, count: 2)
group.interItemSpacing = .fixed(10)
// Section
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
Options?
My guess is that compositional layout simply doesn't support layouts that require a "partial second pass" per group, where the frames of the items can be updated based on information collected during self-sizing the other items in the group (to, for example, force them all to a common height, or align them top/bottom/centered within their group).
If that's the case (not supported), I would love a suggestion for where I might override the layout to provide this capability.
Thank you! ❤️