33

I want my app to be optimized for every accessibility options including the text size.

I made a collectionView layout based on sections with a compositional layout. So I need my cell's height to grow with it's content. I thought using .estimated(constant) would do the job but it doesn't seem to work. The inner constraints seems good to me.

Here is the layout I'm working with :

let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.42), heightDimension: .estimated(90))
let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(90)))
item.contentInsets = NSDirectionalEdgeInsets(top: 5.0, leading: 12.0, bottom: 5.0, trailing: 12.0)
let group = NSCollectionLayoutGroup.vertical(layoutSize: size, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0.0, leading: 6.0, bottom: 0.0, trailing: 0.0)
section.orthogonalScrollingBehavior = .groupPaging

When I set a higher text size on the accessibility settings here is what happens :

enter image description here

The cell is supposed to contain 2 labels here is the autoLayoutConstraints :

    NSLayoutConstraint.activate([
        self.titleLabel.topAnchor.constraint(equalTo: self.container.topAnchor, constant: 10),
        self.titleLabel.leftAnchor.constraint(equalTo: self.container.leftAnchor, constant: 20),
        self.titleLabel.rightAnchor.constraint(equalTo: self.container.rightAnchor, constant: -20)
    ])

    NSLayoutConstraint.activate([
        self.subtitleLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: 10),
        self.subtitleLabel.leftAnchor.constraint(equalTo: self.container.leftAnchor, constant: 20),
        self.subtitleLabel.rightAnchor.constraint(equalTo: self.container.rightAnchor, constant: -20),
        self.subtitleLabel.bottomAnchor.constraint(equalTo: self.container.bottomAnchor, constant: -10)
    ])

Thanks in advance for your help.

Que20
  • 1,469
  • 2
  • 13
  • 27
  • 1
    You may need to listen to the notification `UIContentSizeCategory.didChangeNotification` to refresh the page. – Nullable Oct 15 '19 at 02:03
  • 2
    Thank you for your comment. Unfortunately, I'm not willing to get notified when the font has changed, I'm trying to get a UICollectionViewLayout flexible enough to not have to worry about the accessibility settings to work and display properly. – Que20 Oct 16 '19 at 07:55
  • When your constraints are perfect, changing `label.text` can automatically refresh the constraints, but changing the system font currently does not automatically refresh the constraints, sorry I have not found a source to support this argument. – Nullable Oct 16 '19 at 08:07
  • 1
    The issue does not come from the label's constraints but the `estimated` height I give to cell. Tell me if I'm wrong but, I'm expecting an estimated value setted as height in my NSCollectionLayoutDimension object to work the same as a UICollectionViewFlowLayout.automaticSize. Like you would do here : `flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize` – Que20 Oct 16 '19 at 08:44

4 Answers4

58

What solved the issue for me was to set the same height dimension to both the item and the group.

I know it's the case in the question's shared code, and @Que20's solution definitely helped. But in case the problem persists you might wanna check that.


Example

I'm trying to display a cell containing a single UIButton pinned to the cell's margins. The button has a 44 points high height constraint.

Original Attempt

static func mapSection() -> NSCollectionLayoutSection {
    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1),
        heightDimension: .fractionalHeight(1)) // Here: a fractional height to the item
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                           heightDimension: .estimated(100)) // There: estimated height to the button cell
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

    let section = NSCollectionLayoutSection(group: group)

    return section
}

Despite everything being configured correctly, the button's height constraint breaks and the "estimation" becomes an absolute value.

Button cell at an incorrect size

Successful implementation

static func mapSection() -> NSCollectionLayoutSection {
    /* 
     Notice that I estimate my button cell to be 500 points high
     It's way too much. But since it's an estimation and we're doing things well,
     it doesn't really matter to the end result.
    */
    let heightDimension = NSCollectionLayoutDimension.estimated(500)

    let itemSize = NSCollectionLayoutSize(
        widthDimension: .fractionalWidth(1),
        heightDimension: heightDimension)
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                           heightDimension: heightDimension)
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

    let section = NSCollectionLayoutSection(group: group)

    return section
}

The only real difference lies in the fact that both the item and the group are set to the same estimated height dimension. But it works now!

Button cell at the correct size

Clément Cardonnel
  • 4,232
  • 3
  • 29
  • 36
  • 2
    I can't believe after all this messing around I did, I just had to change my item from fractional to the same estimated height as the group. Thank you! – MattCheetham Feb 26 '21 at 11:55
32

I found that making my LayoutGroup horizontal instead of vertical fixed the issue. Here is my final layout :

let estimatedHeight = CGFloat(100)
let layoutSize = NSCollectionLayoutSize(widthDimension: .estimated(200), heightDimension: .estimated(estimatedHeight))
let item = NSCollectionLayoutItem(layoutSize: layoutSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: layoutSize, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
section.interGroupSpacing = 10
section.orthogonalScrollingBehavior = .groupPaging

Hope it'll help ^^

Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
Que20
  • 1,469
  • 2
  • 13
  • 27
  • 2
    Wow, never would have guessed this, but it worked. The documentation is so lacking. – Christopher Pickslay Jun 26 '20 at 23:38
  • Works great on iOS 14! Thank you so much . I am really pissed off at Apple for this kind of BS, wasted a day on this. Cheers! – VoodooBoot Mar 06 '21 at 19:02
  • 1
    This worked for me as well. But I'm still confused on why did this work? I have a vertical scrolling collection view, so ideally i should layout my group in a vertical fashion right? This solutions seems to have fixed it, but seems a hacky way to do it. – SPS Dec 09 '21 at 18:17
  • Also why didn't it work when i was using vertical group? PS:- Sorry for asking so many questions, Just want to get my concepts on this clear :p – SPS Dec 09 '21 at 18:19
  • THANK YOU!!! This totally fixed my issue!!!! So much wasted time on these things :) – devjme Mar 03 '22 at 22:58
  • Also, when I use a horizontal scrolling collection view, I have to make vertical NSCollectionLayoutGroup. – Omartio Mar 16 '23 at 13:48
17

On iOS13+ if you want to create layout where the cell's constraints determine the height of the cell - try this one:

private func configureCollectionView() {
    collectionView.collectionViewLayout = createLayout()
}

private func createLayout() -> UICollectionViewLayout {
    let layout = UICollectionViewCompositionalLayout {
        (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(10))
        let item = NSCollectionLayoutItem(layoutSize: size)
        item.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: NSCollectionLayoutSpacing.fixed(20), trailing: nil, bottom: NSCollectionLayoutSpacing.fixed(0))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: size, subitems: [item])
        group.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20)
        let section = NSCollectionLayoutSection(group: group)
        return section
    }
    return layout
}

Make sure your constraints and hugging and compression resistance in the cell have priority - 1000 (required), all of them: stackviews, labels etc.

enter image description here enter image description here

You should get something like this:

enter image description here

Or if you want horizontal scrolling with pagination, just add:

section.orthogonalScrollingBehavior = .groupPaging

to your section property, on layout creation and you will get something like this:

enter image description here

Enjoy :)

iOSergey
  • 636
  • 5
  • 5
  • after setting the priority content is able to show but collection going under the navigation bar?. I tried all the solution out there but it didn't work for me – venky Sep 08 '21 at 05:12
9

Not sure it’s relevant to the system accessibility but AFAIK you can’t use .estimated() with .contentInsets on items.

It does work sometimes but unreliably. I’m also fighting this right even when trying to apply the insets to a group (tho it's a subclass of item, would've imagined it'd work) :(

ozzik
  • 91
  • 2