Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • It is clear how to implement full width but dynamic height cells using compositional layout. See this exchange.
  • Known limitations of the flow layout with automatic content size are documented here and here. That is the guy who wrote one of the O'Reilly books. Basically he says that there is no really auto-sizing flow layout even though Apple claims that there is. Hence the quest to try to solve this problem with the Compositional layout.
like image 920
deniz Avatar asked Dec 31 '22 01:12

deniz


1 Answers

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).

like image 196
JWK Avatar answered Jan 18 '23 22:01

JWK