'Compositional layout with centred items and centred paging

Need some help here. I'm building an interesting layout, where cells are centered depending on their count. Probably, best thing to explain is to illustrate a little demo. Here it is:

enter image description here

On the left we have a behaviour of how layout acts depending on items in datasource. On the right - illustration of seven cells in collection view.

I have some thoughts about triple grouping, but probably you can help me with more simple solution using compositional layout.

Thanks!



Solution 1:[1]

Okay, so, I figured out how to make such kind of layout.

Firstly, we need to deal with groups. Here is basic illustration what we need to do:

enter image description here

Probably, at this point you'll have questions, that nice. I can't answer all of them, but here is algorithm:

  1. We need to separate all our items into chunks by 2. So each group must have max 2 items. And max groups in section must be equal to 2
  2. If item if group is less than 2, we apply left inset to group contentInsets.
  3. Section can have orthogonalScrollingBehavior = .groupPagingCentered if needed.

Probably in code it can be achieved in this way:

private func makeLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { [weak self] _, _ -> NSCollectionLayoutSection? in
            guard let self = self else { return nil }
            var nestedGroupHeight: CGFloat = 250

            if numberOfItems > 2 {
                nestedGroupHeight = 500
            }

            let chunkSize = 4 
            let chunks = stride(from: 0, to: numberOfItems, by: chunkSize).map {
                Array($0..<min($0 + chunkSize, numberOfItems)).count
            }

            var allGroups: [NSCollectionLayoutGroup] = []

            for chunk in chunks {

                var group: [NSCollectionLayoutGroup] = []

                // This code probably can be reduced to 2-3 lines.
                // For now - it's for better understanding on what's going on here

                if chunk == 4 { // 4 speakers, two groups of 2
                    group.append(self.createGroup(2))
                    group.append(self.createGroup(2))
                } else if chunk == 3 { // 3 items, one group of 2 and one group of 1
                    group.append(self.getTopSpeakersGroup(2))
                    group.append(self.getTopSpeakersGroup(1))
                } else if chunk == 2 { // 2 items, one group of 2
                    group.append(self.getTopSpeakersGroup(2))
                } else if chunk == 1 { // 1 items, one group of 1
                    group.append(self.getTopSpeakersGroup(1))
                }

                let mainGroup = NSCollectionLayoutGroup.vertical(
                    layoutSize: NSCollectionLayoutSize(
                        widthDimension: .absolute(UIScreen.main.bounds.width - 45),
                        heightDimension: .estimated(nestedGroupHeight)),
                    subitems: group
                )

                group.interItemSpacing = .fixed(10)

                allGroups.append(group)
            }

            let nestedGroup = NSCollectionLayoutGroup.horizontal(
                layoutSize: NSCollectionLayoutSize(
                widthDimension: .absolute(UIScreen.main.bounds.width - 45),
                heightDimension: .estimated(nestedGroupHeight)),
                subitems: allGroups)

            nestedGroup.interItemSpacing = .fixed(15)

            let layoutSection = NSCollectionLayoutSection(group: nestedGroup)
            layoutSection.orthogonalScrollingBehavior = .groupPagingCentered

            return layoutSection

        }

    }

    private func createGroup(_ count: Int) -> NSCollectionLayoutGroup {
        let itemWidth = BoardTopMemberCell.width
        let groupHeight: CGFloat = 250
        let groupWidth = UIScreen.main.bounds.width - 45

        let trailingItem = NSCollectionLayoutItem(
            layoutSize: NSCollectionLayoutSize(
                widthDimension: .absolute(itemWidth),
                heightDimension: .estimated(groupHeight)
            )
        )

        var leftInset: CGFloat = groupWidth / 2 - 15/2 - itemWidth

        if count < 2 {
            leftInset = groupWidth / 2 - itemWidth / 2
        }

        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth),
                                               heightDimension: .estimated(groupHeight))

        let trailingGroup = NSCollectionLayoutGroup.horizontal(
            layoutSize: groupSize,
            subitems: [trailingItem]
        )
        trailingGroup.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: leftInset, bottom: 0, trailing: 0)
        trailingGroup.interItemSpacing = .fixed(15)

        return trailingGroup
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pete Streem