I've a UICollectionView with a grid layout of 2 columns. I've used WaterfallTrueCompositionalLayout by eeshishko for the compositional 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.
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.
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.


