UICollectionView Compositional Layout Tag Group

I’m trying to create a layout with a group of labels / tags. The labels have a flexible width and height. If a label exceeds the width of the container it should grow in height. I’m using a UICollectionView with a compositional layout to create this layout.

At first I used estimated for the width and height dimension of the layout items within a horizontal group layout that had an fractionalWidth and an estimated height. This worked but only if the cells with the labels were smaller than the group width. If they are larger, the app crashes. Somewhere on StackOverflow I read that this is a known issue.

To fix the issue above I made the main group a vertical layout and created a different group for every row of labels. If the labels width exceeds the width of the group I give if a fractional width and if it’s smaller I give it an estimated width. This approach prevents the crashed above and gives me my desired layout.

But… as soon as I rotate the device of call invalidate on the collection view layout; the layout is messed up. It looks like very cell gets the width of the value I proved in the estimated width dimension.

Can someone help me to fix this issue? My app needs to run on iOS 14 and above.

I've uploaded a small demo app to my GitHub that contains my current setup: https://github.com/JeroenVanRijn/compositional-layout

This is the code I use for the UICollectionViewCompositionalLayout:

    func createLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewCompositionalLayout {
            (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection in

            var groups = [NSCollectionLayoutGroup]()
            let contentSize = layoutEnvironment.container.effectiveContentSize
            
            var rows = [[CGFloat]]()
            var currentRow = [CGFloat]()
            var currentRowWidth: CGFloat = 0
            let spacing: CGFloat = 10
            let maxRowWidth = contentSize.width - (2 * spacing)
            
            // Loop through the labels and add them to rows.
            for label in self.dataSource.snapshot(for: .main).items {
                let textWidth = self.width(for: label.text)
                let cellWidth = textWidth + 16
                
                if currentRowWidth > 0 {
                    currentRowWidth += spacing // spacing
                }
                
                currentRowWidth += cellWidth
                currentRow.append(cellWidth)
                
                if currentRowWidth > maxRowWidth {
                    let last = currentRow.last!
                    let withoutLast = Array(currentRow.dropLast())
                    
                    addRow(items: withoutLast)

                    currentRow = [last]
                    currentRowWidth = last
                }
            }
            
            // Remaining rows
            if !currentRow.isEmpty {
                addRow(items: currentRow)
            }
            
            func addRow(items: [CGFloat]) {
                rows.append(items)
                
                // If a row contains only item that is larger that the group with we give it the width of the group.
                if items.count == 1 && items[0] >= maxRowWidth {
                    print("Add row: \(items.count) - FULL WIDTH")
                    
                    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(44))
                    let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
                    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(44))
                    let innerGroup = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [layoutItem])
                    innerGroup.interItemSpacing = .fixed(spacing)
                    groups.append(innerGroup)
                    
                } else {
                    print("Add row: \(items.count)")
                    
                    let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(44), heightDimension: .estimated(44))
                    let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
                    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(44))
                    let innerGroup = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [layoutItem])
                    innerGroup.interItemSpacing = .fixed(spacing)
                    groups.append(innerGroup)
                }
            }
            
            print("Number of groups: \(groups.count)")
            
            let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(44))
            let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: groups)
            group.interItemSpacing = .fixed(spacing)
            
            let section = NSCollectionLayoutSection(group: group)
            section.interGroupSpacing = spacing
            section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
            
            return section
        }
        return layout
    }
    
    func width(for text: String) -> CGFloat {
        text.size(withAttributes:[.font: UIFont.systemFont(ofSize: 16)]).width
    }