OC-UICollectionView Implementing Waterfall Flow

Posted by Irresistable on Fri, 14 Jun 2019 21:00:35 +0200

UICollectionView Implementing Waterfall Flow

There are two known schemes for realizing waterfall flow in iOS:

  1. Uses the UIScrollView to encapsulate a set by oneself, this kind of plan applies before iOS 6, because iOS 6 only then comes out UICollection View, but now this kind of plan has not been used very much, must encapsulate by oneself. And the performance of self-encapsulation is not necessarily better.
  2. Use the UICollection View that comes with the system, and then customize the layout to realize the waterfall flow effect.

In this paper, we introduce the second implementation scheme.
First we need to customize a layout inherited from UICollectionViewLayout, and then we need to rewrite four methods:

  1. (void)prepareLayout
  2. (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
  3. (UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
  4. (CGSize)collectionViewContentSize

The first method is to do some initialization. This method must first call the implementation of the parent class.
The second method returns an array with UICollectionViewLayoutAttributes
The third method returns UICollectionViewLayoutAttributes at indexPath location
The fourth method is to return the scrollable range of UICollectionView

How to Realize Waterfall Flow

First of all, we need to understand the arrangement of the waterfall flow. The waterfall flow is irregular in size. Some controls are distributed on the screen of the mobile phone. Then there must be tall ones and short ones (like human height, haha ha). When the first row is filled, they will continue to row down. Where should this be put? === The answer is to put it under the shortest one in the first row.== By analogy, arrange them according to this rule.
With that in mind, it's time to write code.
First, we create two arrays, one containing the layout attributes of the cell and the other containing the total height of the current cell.

//c Stores the layout attributes of all cell s
@property (nonatomic, strong) NSMutableArray *attrsArray;
//The current height of all columns
@property (nonatomic, strong) NSMutableArray *columnHeights;
/** Content Height */
@property (nonatomic, assign) CGFloat contentHeight;

  

- (void)prepareLayout
{
    [super prepareLayout];

    self.contentHeight = 0;

    //Clear all the heights calculated before, because this method is called back when refreshed
    [self.columnHeights removeAllObjects];
    for (NSInteger i = 0; i < DefaultColumnCpunt; i++) {
        [self.columnHeights addObject:@(self.edgeInsets.top)];
    }

    //Put all the initialization operations here.
    [self.attrsArray removeAllObjects];

    //Start creating layout attributes for each cell
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        // Create a location
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // Get the layout attributes corresponding to the indexPath location cell
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

Set the cell's height to self.edgeInsets.top first or it will crash here. Then take out the number of items, and loop out the UICollection View Layout Attributes for each item, then add it to attsArray, and return directly to attrsArray in the -(NSArray < UICollection View Layout Attributes *>*) layoutAttributes ForElements Int:(CGRect) rect method.

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    CGFloat collectionViewW = self.collectionView.frame.size.width;

    CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right -(self.columnCount - 1) * self.columnMargin) / self.columnCount;

    CGFloat h = [self.delegate WaterFlowLayout:self heightForRowAtIndexPath:indexPath.item itemWidth:w];

    NSInteger destColumn = 0;

    CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
    for (NSInteger i = 0; i < self.columnCount; i++) {
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];

        if (minColumnHeight > columnHeight) {
            minColumnHeight = columnHeight;
            destColumn = i;
        }
    }

    CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin);
    CGFloat y = minColumnHeight;
    if (y != self.edgeInsets.top) {
        y += self.rowMargin;
    }

    attrs.frame = CGRectMake(x, y, w, h);

    self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));

    CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
    if (self.contentHeight < columnHeight) {
        self.contentHeight = columnHeight;
    }
    return attrs;

}

The above method is to calculate the location of item code, first take out the UICollectionViewLayoutAttributes object of indexPath, and then take out w, h, the core code is as follows:

NSInteger destColumn = 0;

    CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
    for (NSInteger i = 0; i < self.columnCount; i++) {
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];

        if (minColumnHeight > columnHeight) {
            minColumnHeight = columnHeight;
            destColumn = i;
        }
    }

    CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin);
    CGFloat y = minColumnHeight;
    if (y != self.edgeInsets.top) {
        y += self.rowMargin;
    }

First, we make a mark destColumn to record which column it is, then define a minColumn Height as the minimum column height, take out the height of self. column Heights [0], which is the smallest by default, and then go through the for loop to get the height above the i position, if the value is less than the previous minColumn Height, then take out the height. Degree is the smallest height, and then the value of i is assigned to destColumn, and then the value of x is the result of the addition in the code above, and the value of y is to continue adding spacing.

- (CGSize)collectionViewContentSize
{
//    CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
//    
//    for (NSInteger i = 1; i < DefaultColumnCpunt; i++) {
//        // Get the height of column i
//        CGFloat columnHeight = [self.columnHeights[i] doubleValue];
//        
//        if (maxColumnHeight < columnHeight) {
//            maxColumnHeight = columnHeight;
//        }
//    }
    return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
}

Portal: gitHub

Topics: iOS Mobile less github