deniz
deniz

Reputation: 975

Is it possible to create cells with dynamic width but fixed height using Compositional Layout

TLDR; Depending on the section, I need the cells to be sized either:

  1. Fixed width but dynamic height (for example, full screen width with automatic height depending on the content)
  2. Dynamic width but fixed height (1st cell could be 120pt wide, 2nd cell could be 155pt wide, etc..)

Here is a pictogram of what we need to clear any ambiguity that might exists. enter image description here

I am looking for simply: "Yes, you can achieve this with compositional layout and here is how...". Or "No, you cannot implement this with compositional, you must use flow layout or some other custom layout."

Couple of additional requirements:

  1. Proposed solution should NOT recommend using collectionView(_:layout:sizeForItemAt:) with flow layout because that is the thing we are trying to avoid. If that is the answer, then you may simply say - "No, this cannot be achieved with compositional layouts".
  2. Proposed solution should let the collection view figure out how to lay the cells appropriately based on their intrinsic content size. This is akin to using tableview's dynamic self sizing capability by UITableViewAutomaticDimension

Background info:

Upvotes: 10

Views: 8093

Answers (1)

JWK
JWK

Reputation: 3780

You can build the "section 2" layout using UICollectionViewCompositionalLayout. Here's how:

let layoutSize = NSCollectionLayoutSize(
    widthDimension: .estimated(100),
    heightDimension: .absolute(32)
)

let group = NSCollectionLayoutGroup.horizontal(
    layoutSize: .init(
        widthDimension: .fractionalWidth(1.0),
        heightDimension: layoutSize.heightDimension
    ),
    subitems: [.init(layoutSize: layoutSize)]
)
group.interItemSpacing = .fixed(8)

let section = NSCollectionLayoutSection(group: group)
section.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
section.interGroupSpacing = 8

return .init(section: section)

The important part is that the group takes up the available width using .fractionalWidth(1.0), but the subitems have an estimated width. Just make sure your collection view cell can be self-sized by overriding sizeThatFits(_:), preferredLayoutAttributesFitting(_:) or using Auto Layout. The result will be a layout that looks like this:

enter image description here

To use "section 1" and "section 2" in the same UICollectionViewCompositionalLayout, just create your layout using init(sectionProvider:), where you return the appropriate NSCollectionLayoutSection for the current section identifier:

func createCollectionViewLayout() -> UICollectionViewCompositionalLayout {
    .init(sectionProvider: { [weak self] sectionIndex, _ in
        guard let sectionIdentifier = self?.dataSource.snapshot().sectionIdentifiers[sectionIndex] else { return nil }
        switch sectionIdentifier {
        case .section1: return self?.createSection1()
        case .section2: return self?.createSection2()
        }
    })
}

It's worth noting you may see some odd behavior with this layout if your view controller is presented using the default pageSheet style on iOS 13+. I have a related question about that here (using a slightly different layout).

Upvotes: 17

Related Questions