I am trying to create a compositional layout comprised of a group of 2 items, plus another group of 1 item. I would like each group to occupy the full screen width, and page-scroll between them. So the 2 items in the first group (Items A) would each occupy 50% of the screen width, and the 1 item (Item B) in the second group would occupy 100% of the screen width. I have the collection view pinned to the left and right edges of its superview, which occupies the entire screen.
A textual diagram would appear something like this:
[Item A][Item A]|[ Item B ]
Where "|" denotes the edge of the screen. So depending on scroll position, either both Items A will be visible, or Item B will be visible. Item A and Item B should never be visible at the same time after scrolling has ended.
I am using the following code for my compositional layout:
UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let planItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))
let planItem = NSCollectionLayoutItem(layoutSize: planItemSize)
let planGroup = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: planItem, count: 2)
let formItem = NSCollectionLayoutItem(layoutSize: groupSize)
let formGroup = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: formItem, count: 1)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [planGroup, formGroup])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPaging
return section
}
The above code is resulting in a layout that looks like this:
[Item A][Item A]|[Item B]
It is nearly correct except that Item B is being sized at 50% width of the screen instead of 100%. I can't figure out what's wrong with the layout, but it appears to be using planItemSize
for all items, instead of just for Items A. How do I get Item B to be sized at 100% the screen width?
First, we could do this very easily with a Horizontal Flow Layout, .isPagingEnabled = true
, and implementing sizeForItemAt
:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let w: CGFloat = collectionView.frame.width
let h: CGFloat = collectionView.frame.height
if indexPath.item % 3 == 2 {
return .init(width: w, height: h)
}
return .init(width: w * 0.5, height: h)
}
and we're done :)
However, since you're asking about UICollectionViewCompositionalLayout
, let's look at that.
When working with items, groups and sub-groups, .fractionalWidth()
and .fractionalHeight()
is relative to the parent.
Since we're using 100%
for all heights in this layout, we'll focus on widths.
For a "basic" layout, .fractionalWidth(1.0)
will be 100% of the width of the collection view.
Using .fractionalWidth(0.5)
will, of course, be 50% of the width of the collection view.
When we put an items in a group, the count:
affects the size and the item width is (apparently) ignored.
So, if we set a group to .fractionalWidth(1.0)
(100% of the width of the collection view), and set count to 4:
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1))
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 4)
let section = NSCollectionLayoutSection(group: group)
return section
We'll get this (red outline is collection view frame):
We can change itemSize
.fractionalWidth(1.0)
to 0.1
, 0.5
, 2.0
, etc... it doesn't change the layout.
That said, in your current code, you are setting both planGroup
and formGroup
to 100%
width of their parent - group
- which is also 100%
of the width of the collection view.
So, only the first group fits, and the second group is never used.
Let's fix that (I'm going to change group
to mainGroup
to make it easier to talk about).
What we can do instead is set planGroup
to .fractionalWidth(0.5)
and formGroup
to .fractionalWidth(0.5)
... and we'll get close:
But - how do we get two items to fill the collection view, followed by one item, followed by two items, etc?
We can set the mainGroup
width to 200% of the collection view width:
Now we get the desired "pattern." Note that we will want to use .paging
instead of .groupPaging
because we want to page by collection view Width:
Here is a modified version of your code - you should be able to drop it right in as a replacement:
UICollectionViewCompositionalLayout {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
// main group size is TWICE as wide as the collection view
let mainGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(2.0), heightDimension: .fractionalHeight(1))
// sub-groups
// width is Percentage-of-Parent
// so if each sub-group is one-half the width of the main group
// each will be the Full width of the collection view
let planGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))
let formGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))
// because we're putting TWO planItems in planGroup
// CompositionLayout will use 50% of planGroup width for each item
// so planItemSize width is ignored
let planItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1))
let planItem = NSCollectionLayoutItem(layoutSize: planItemSize)
let planGroup = NSCollectionLayoutGroup.horizontal(layoutSize: planGroupSize, subitem: planItem, count: 2)
// because we're putting ONE formItem in formGroup
// CompositionLayout will use 100% of formGroup width
// so formItemSize width is ignored
let formItemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1))
let formItem = NSCollectionLayoutItem(layoutSize: formItemSize)
let formGroup = NSCollectionLayoutGroup.horizontal(layoutSize: formGroupSize, subitem: formItem, count: 1)
let mainGroup = NSCollectionLayoutGroup.horizontal(layoutSize: mainGroupSize, subitems: [planGroup, formGroup])
let section = NSCollectionLayoutSection(group: mainGroup)
// use .paging instead of .groupPaging
// because we want to page by collection view Width
section.orthogonalScrollingBehavior = .paging
return section
}