0

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.
deniz
  • 371
  • 3
  • 8
  • You need to determine the cell size BEFORE you configure the cell. So after when you get your data for the cells... but before loading the collectionview... add some function to determine what size cell it should have (store this data in an array). Within the sizeForItem function of UICollectionViewFlowLayoutDelegate, you can include logic referencing the section and the cell size based on indexpath. – nicksarno May 24 '20 at 05:06
  • Thanks for the information - I realize that my question wasn't clear. We are not interested in that particular path using FlowLayout and collectionView(_:layout:sizeForItemAt:) delegate method. Thank you for taking the time to respond though. – deniz May 24 '20 at 17:39
  • No worries! Good luck trying to figure it out with compositional layout. I think this is new and not sure how customizable it really is. If you decide otherwise, I do believe what you are trying to accomplish can easily be done with sizeForItemAt. I set up collectionViews with different sized cells based on dynamic content all the time. Cheers – nicksarno May 24 '20 at 20:17

1 Answers1

7

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

JWK
  • 1,988
  • 12
  • 24
  • this is quite encouraging. I will try your recommendation and post the results here. thanks for taking the time post an answer here. I also checkout your related question, good to know. – deniz May 28 '20 at 01:53
  • I am happy to confirm that this does work. I think I will write a proper blog about it at some point. I obviously simplified the question here to get the crux of the problem. I will write the full solution when I get a chance. – deniz May 31 '20 at 17:07