1

Here is the preview of layout

As you can see this layout has a pattern of cells 0, 1, 2, 0, 1, 2, 0, 1, 2 with respect to the size. I want my layout to depict 0, 1, 2, 1, 2, 1, 2, 1, 2 as the pattern with respect to size. In other words, just have the first cell to be of large size and the others should take up a size to fit the row of 2. My current code for the layout:

import UIKit

struct Layout {
    
    private struct LayoutConstants {
        static let largeGroupHeight: CGFloat = 406
        static let leadingInset: CGFloat = 8
        static let topInset: CGFloat = 10
        static let bottomInset: CGFloat = 15
        static let itemCount = 2
        static let fractionalConstant: CGFloat = 1.0
        static let fractionalWidthLeading: CGFloat = 0.67
        static let fractionalWidthTrailing: CGFloat = 1.0
        static let fractionalHeightTrailing: CGFloat = 0.3
        static let fractionalContainerWidth: CGFloat = 1.2
        static let groupFractionalHeight: CGFloat = 0.33
    }
    
    func layoutSection() -> NSCollectionLayoutSection {
        let leadingItem = NSCollectionLayoutItem(
            layoutSize: NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(LayoutConstants.fractionalWidthLeading),
                heightDimension: .fractionalHeight(LayoutConstants.fractionalConstant)))
        leadingItem.contentInsets = NSDirectionalEdgeInsets(top: LayoutConstants.topInset,
                                                            leading: LayoutConstants.topInset,
                                                            bottom: LayoutConstants.bottomInset,
                                                            trailing: LayoutConstants.leadingInset)
        
        let trailingItem = NSCollectionLayoutItem(
            layoutSize: NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(LayoutConstants.fractionalWidthTrailing),
                heightDimension: .fractionalHeight(LayoutConstants.fractionalHeightTrailing)))
        trailingItem.contentInsets = NSDirectionalEdgeInsets(top: LayoutConstants.topInset,
                                                             leading: LayoutConstants.leadingInset,
                                                             bottom: LayoutConstants.bottomInset,
                                                             trailing: LayoutConstants.leadingInset)

        let trailingGroup = NSCollectionLayoutGroup.vertical(
            layoutSize: NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(LayoutConstants.groupFractionalHeight),
                heightDimension: .fractionalHeight(LayoutConstants.fractionalConstant)),
            subitem: trailingItem,
            count: LayoutConstants.itemCount)
        
        var subitems: [NSCollectionLayoutItem] = []
        subitems.append(leadingItem)
        subitems.append(trailingGroup)
        
        let containerGroup = NSCollectionLayoutGroup.horizontal(
            layoutSize: NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(LayoutConstants.fractionalContainerWidth),
                heightDimension: .absolute(LayoutConstants.largeGroupHeight)),
            subitems: subitems)
        let section = NSCollectionLayoutSection(group: containerGroup)
        section.orthogonalScrollingBehavior = .continuous
        return section
    }
}
CoderSulemani
  • 123
  • 11

1 Answers1

0

I found different approach to do it as following:

  1. I will use two sections.
    Section1 for the first number, it will hold a group of one item.
    Section2 for everything else, it will hold a group of two items (stacked vertically).
  2. We need to change the scroll direction of collection view to horizontal.
    Normally all sections are stacked vertically, so the two sections will be stacked vertically and won't give us what we want.
  3. We need to change the layout configurations for groups and items so it can work correctly for the horizontal scroll direction.

That's it, now for the code.

ADVICE: Please try to gradually move from your solution to mine to learn the different issues I learned while testing, it will help you understand why for each piece.

NOTE: I used values instead of your constants so it doesn't confuse you, because now constants need to be changed/renamed to fit this approach.

  • Section1 (One group of one item)
func createSection1() -> NSCollectionLayoutSection {
    let item = NSCollectionLayoutItem(
        layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalHeight(1.0)
        )
    )
    item.contentInsets = NSDirectionalEdgeInsets(
        top: 10,
        leading: 10,
        bottom: 15,
        trailing: 8
    )
    
    let group = NSCollectionLayoutGroup.horizontal(
        layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(0.67),
            heightDimension: .absolute(406)
        ),
        subitem: item,
        count: 1
    )
    
    return NSCollectionLayoutSection(group: group)
}
  • Section2 (One group of two items stacked vertically)
func createSection2() -> NSCollectionLayoutSection {
    
    let item = NSCollectionLayoutItem(
        layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .fractionalHeight(0.3)
        )
    )
    item.contentInsets = NSDirectionalEdgeInsets(
        top: 10,
        leading: 8,
        bottom: 15,
        trailing: 8
    )
    
    let group = NSCollectionLayoutGroup.vertical(
        layoutSize: NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(0.33),
            heightDimension: .absolute(406)
        ),
        subitem: item,
        count: 2
    )
    
    let section = NSCollectionLayoutSection(group: group)
    return section
}
  • The final layout
    Please notice the line indicating scrollDirection change
func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout {
        [weak self] (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        guard let self = self else { return nil }
        
        if sectionIndex == 0 {
            return self.createSection1()
        } else {
            return self.createSection2()
        }
    }
    
    // Changing scrollDirection
    let config = UICollectionViewCompositionalLayoutConfiguration()
    config.scrollDirection = .horizontal
    layout.configuration = config
    
    return layout
}
  • Finally, here as suggession of how to handle it in datasource
func numberOfSections(in collectionView: UICollectionView) -> Int {
    2
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    data.isEmpty
        ? 0
        : section == 0 ? 1 : data.count - 1
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(
        withReuseIdentifier: String(describing: Cell.self),
        for: indexPath
    ) as! Cell
    
    if indexPath.section == 0 {
        cell.number = data[0]
        cell.color = colors[0]
    } else {
        cell.number = data[indexPath.row + 1]
        cell.color = colors[indexPath.row + 1]
    }
    
    return cell
}
  • Demo

demo

That's it, please let me know if you have any question.

Ahmed Shendy
  • 1,424
  • 15
  • 28
  • Thanks for the solution Ahmed! I had a detail which I probably missed is that my project has a vertical collection view and each of the sections have their own compositional layouts. This layout is one of those sections, and therefore I wouldn't be able to use a multiple section solution. I would probably do this in the refactoring on the feature though. Thanks again :) – CoderSulemani Feb 14 '22 at 08:22
  • @CoderSulemani Umm, why not make two cells, first cell for this layout, and second cell for others, then you make first cell composed of its own collectionView with this layout of two horizontal sections. – Ahmed Shendy Feb 14 '22 at 08:54
  • Yes I thought of the same, and hopefully will do that while refactoring. Thought of asking this question just to see if there were any simpler ways to resolve this issue :) – CoderSulemani Feb 14 '22 at 15:35
  • @CoderSulemani I see. Based on my understanding so far, section and item are physical elements, but group is just a virtual element describing how some items organized in a section .. so basically you just have one physical element, named section, composed of one or more items, organized in one or more group layout. That's why I started to find another approach. – Ahmed Shendy Feb 15 '22 at 01:22
  • @CoderSulemani I also thought to ask you if your scenario is just one big item, followed by known number of vertically stacked items, you can make a fixed number of items in the section organized in two group layouts. – Ahmed Shendy Feb 15 '22 at 01:25
  • @CoderSulemani Also I read somewhere that you could build/rebuild collectionView layout once data is fetched .. so you will know about how many vertically stacked items in this first section. Like that the items in first section are dynamic, I think this will serve you well in current project state. – Ahmed Shendy Feb 15 '22 at 01:28
  • Yes, this will be good for my current state I think! Thanks for the help :) – CoderSulemani Feb 15 '22 at 08:44