0

Goal:

UICollectionView with 'waterfall effect', entailing:

  1. Multiple columns of cells (each cell contains only one stackview).

  2. Each stackview contains n items, thus height of cells varies, but width is fixed.

  3. Each cell in each column should butt up against one above it, with fixed padding, let's say CGFloat(50.0).

  4. That produces a 'staggered' look across the columns, but no wasted space or 'empty slots'.


The Problem:

I've tried configuring the collection view using UICollectionViewFlowLayout, and alternatively, UICollectionViewCompositionalLayout without success Best I've been able to achieve is cells aligned to top of each row, where each row has a uniform height (approx same height as tallest cell in that row). Meaning, next row starts below the bottom of the tallest cell in the previous row, leaving large gaps between shorter cells and cells placed cells in the next row (which is what I don't want).


Afterthoughts:

I suspect that's just how it is... that collection view (particularly compositional layout) doesn't know how to handle multiple columns with independent vertical layouts wherein all columns scroll vertically as one.

I'm not even sure if a custom layout could solve it, as I've never written one yet. Or if I'm just better off creating a big scroll view and layout the stackviews manually without a collection view, or multiple single-column collection views side by side? I don't understand enough of the tradeoffs and possibilities and hoping someone can steer me in the right direction.

Note, I did find this cool hack for making the content of cells top align for UICollectionViewFlow layout, and it works for what it is, but doesn't but doesn't give me the waterfall effect I'm looking e.g. doesn't tie multiple rows together for a seamless vertically sized cell height appearance.

UICollection View Flow Layout Vertical Align

class TopAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        // This method is being requested to return the layout attributes for each cell in the rectangle.'
        // where the attributes are frame, bounds, center, size, transform3D, transform, alpha, zIndix and isHidden

        // It starts by getting a copy attributes the super class has configured each cell:
        //
        let attributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() } as? [UICollectionViewLayoutAttributes]

        attributes?
            // Then it's using the reduce() function to build a new dictionary of tupples consisting of:
            // [cgfloat : (cgfloat, [attrs])], which is [ cell center.y : (cell origin.y, [all the other cells]) ]
        ///
            .reduce([CGFloat: (CGFloat, [UICollectionViewLayoutAttributes])]()) { // for each cell, $0 = dict to build, $1 = next cell
                guard $1.representedElementCategory == .cell else { return $0 }   // only operate on cells (not decorations or supplemental views)
                return $0.merging([ceil($1.center.y): ($1.frame.origin.y, [$1])]) { // center.y : origin.y, [merged attributes]
                    ($0.0 < $1.0 ? $0.0 : $1.0, $0.1 + $1.1)
                        //
                        // if dict center.y < cell center.y
                        //    (cell ctr.y, [cell/dict merged attributes]
                        // else
                        //    (dict ctr.y, [cell/dict merged attributes]

                }
            } // returns dictionary of merged [cgfloat : (cgfloat, [attrs])]
            .values.forEach { minY, line in // pull each item up to the top via diff btw center and top line
                line.forEach {
                    $0.frame = $0.frame.offsetBy(
                        dx: 0,
                        dy: minY - $0.frame.origin.y
                    )
                    $0.frame = CGRectMake($0.frame.origin.x, $0.frame.origin.y + 200, $0.frame.size.width, 50) //$0.frame.size.height - (minY - $0.frame.origin.y))
                }
            }

        return attributes
    }
}
clearlight
  • 12,255
  • 11
  • 57
  • 75
  • How many cells (stack views) total will there be? Maybe a collection view isn't a good choice. Maybe each column should be a master stack view containing the stack views for the column. Then put the master stack views side by side in a scroll view. – HangarRash Dec 16 '22 at 02:55
  • @HangarRash Probably 50 at the outside. Usually less Each containing probably 20 or fewer entries. Thanks for suggesting an alternative. – clearlight Dec 16 '22 at 04:19

0 Answers0