Waterfall UICollectionViewFlowLayout with expanding cell

32 views Asked by At

I've a UICollectionView with a grid layout of 2 columns. I've used WaterfallTrueCompositionalLayout by eeshishko for the compositional layout.

Grid layout

Cells are expandable on tap of a button inside cell, the expanded cell takes up double it's height.

Currently when one cell gets expanded, the cells after it shuffles to make room for expanded cell, with animations. Attached screenshot below.

Current behaviour

However, the designer wants a different behaviour. They want only the affected column to shift down to make space for expanding cell, while adjacent column should remain unaffected. Like in screenshot below.

Expected behaviour

I see this is a tricky situation and managing indexes is difficult. How to proceed for the solution.

Here's the code

    private func setupCollectionView() {
        let configuration = WaterfallTrueCompositionalLayout.Configuration(
            columnCount: 2,
            interItemSpacing: 0,
            contentInsetsReference: .automatic,
            itemCountProvider: { [unowned self] in
                return self.items
            },
            itemHeightProvider: { [unowned self] row, _ in
                if let currentValue = expandedIndexes[row], currentValue {
                    return self.cardHeight*2
                }
                return self.cardHeight
            }
        )

        let layout = UICollectionViewCompositionalLayout { sectionIndex, environment in
            let section = WaterfallTrueCompositionalLayout.makeLayoutSection(
                config: configuration,
                environment: environment,
                sectionIndex: sectionIndex
            )
            return section
        }
        collectionView.setCollectionViewLayout(layout, animated: true)
        collectionView.delegate = self
        collectionView.dataSource = self
    }

Waterfall layout calculations


extension WaterfallTrueCompositionalLayout {
    final class LayoutBuilder {
        private var columnHeights: [CGFloat]
        private let columnCount: CGFloat
        private let itemHeightProvider: ItemHeightProvider
        private let interItemSpacing: CGFloat
        private let collectionWidth: CGFloat

        init(
            configuration: Configuration,
            collectionWidth: CGFloat
        ) {
            self.columnHeights = [CGFloat](repeating: 0, count: configuration.columnCount)
            self.columnCount = CGFloat(configuration.columnCount)
            self.itemHeightProvider = configuration.itemHeightProvider
            self.interItemSpacing = configuration.interItemSpacing
            self.collectionWidth = collectionWidth
        }

        func makeLayoutItem(for row: Int) -> NSCollectionLayoutGroupCustomItem {
            let frame = frame(for: row)
            columnHeights[columnIndex()] = frame.maxY + interItemSpacing
            return NSCollectionLayoutGroupCustomItem(frame: frame)
        }

        func maxColumnHeight() -> CGFloat {
            return columnHeights.max() ?? 0
        }
    }
}

private extension WaterfallTrueCompositionalLayout.LayoutBuilder {
    private var columnWidth: CGFloat {
        let spacing = (columnCount - 1) * interItemSpacing
        return (collectionWidth - spacing) / columnCount
    }

    func frame(for row: Int) -> CGRect {
        let width = columnWidth
        let height = itemHeightProvider(row, width)
        let size = CGSize(width: width, height: height)
        let origin = itemOrigin(width: size.width)
        return CGRect(origin: origin, size: size)
    }

    private func itemOrigin(width: CGFloat) -> CGPoint {
        let yCoordinate = columnHeights[columnIndex()].rounded()
        let xCoordinate = (width + interItemSpacing) * CGFloat(columnIndex())
        return CGPoint(x: xCoordinate, y: yCoordinate)
    }

    private func columnIndex() -> Int {
        columnHeights
            .enumerated()
            .min(by: { $0.element < $1.element })?
            .offset ?? 0
    }
}

Thanks for your time.

0

There are 0 answers