UICollectionView is used to achieve a randomly amplified irregular waterfall stream. The image aspect ratio and content are returned by the server interface. The effect is shown as follows:

Interface:

https://www.easy-mock.com/mock/5cff89e36c54457798010709/shop/finderlist
Copy the code

Data:

{
  "data": [{"img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561019906083&di=2bbf7db2124067fe80739cce43a2b00e&i mgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201410%2F05%2F20141005095943_QY5e8.jpeg"."width": "1200"."height": "2249"
    },
    {
      "img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561020045178&di=56eb95088ce1a23bbd16776ebcedb837&i mgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2Fc8107b13c3bfd1fa8835f5dc80c541b64c6b9e901a8f7-RLJBJP_fw658"."width": "658"."height": "872"},... {"img": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561020635692&di=cd0dedd961380917af46c536e7f6600b&i mgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201707%2F29%2F20170729215622_tTLBP.thumb.700_0.jpeg"."width": "700"."height": "701"}}]Copy the code

Each image data specifies the width and height of the image. Due to the need for enlargement, the top of the image that is enlarged and occupies the width of two columns must be aligned, so the height of the two columns with little difference in height needs to be corrected.

  • If the difference between the insertion height and the left and right height of the image is less than 20% of its height, the image height will be forcibly aligned.
  • No two consecutive enlarged images are allowed in each row.
  • No consecutive enlarged images are allowed in each column.
  • Use network interface data
  • Waterfall streams use pictures to define dimensions
  • Supports drop-down refresh
  • Supports custom column number
  • Supports custom picture spacing
  • Supports custom outer border spacing

The specific implementation

1. Rewriting methods

Create a subclass of UICollectionViewLayout

@ protocol JKRFallsLayoutDelegate < NSObject > @ optional / / / the number of columns - columnCountInFallsLayout: (JKRFallsLayout (CGFloat) *)fallsLayout; / / / column spacing - (CGFloat) columnMarginInFallsLayout (fallsLayout JKRFallsLayout *); /// line spacing - (CGFloat)rowMarginInFallsLayout:(JKRFallsLayout *)fallsLayout; // collectionView - (UIEdgeInsets)edgeInsetsInFallsLayout:(JKRFallsLayout *)fallsLayout; - (JKRImageModel *)modelWithIndexPath:(NSIndexPath *)indexPath; @end @interface JKRFallsLayout : UICollectionViewLayout @property (nonatomic, weak) id<JKRFallsLayoutDelegate> delegate; @endCopy the code

Override the following methods:

// collectionView is called when it is first laid out and then relaid. PrepareLayout is called only when the data source changes - (void)prepareLayout {// Override must call super method [super prepareLayout]; } // return the layout property, A UICollectionViewLayoutAttributes array of objects - (NSArray < UICollectionViewLayoutAttributes * > *)layoutAttributesForElementsInRect:(CGRect)rect {return[super layoutAttributesForElementsInRect:rect]; } / / computing layout properties - (UICollectionViewLayoutAttributes *) layoutAttributesForItemAtIndexPath: (indexPath NSIndexPath *) {return[super layoutAttributesForItemAtIndexPath:indexPath]; } / / return collectionView ContentSize - (CGSize) collectionViewContentSize {return [super collectionViewContentSize];
}
Copy the code

2. Layout calculation

To evaluate the layout, you need to create the following properties

@property (nonatomic, strong) NSMutableArray<UICollectionViewLayoutAttributes *> *attrsArray; //< all cell layout @property (nonatomic, strong) NSMutableArray *columnHeights; //< height of each column @property (nonatomic, assign) NSInteger noneDoubleTime; @property (nonatomic, assign) NSInteger lastDoubleIndex; ///< last large size column number - (CGFloat)columnCount; ///< column number - (CGFloat)columnMargin; ///< column margin - (CGFloat)rowMargin; //< line margin - (UIEdgeInsets)edgeInsets; / / / < collectionView marginCopy the code

The – (void)prepareLayout method iterates through the cells to be calculated, calls the method that calculates the layout, and saves the obtained layout properties into the attrsArray array:

- (void)prepareLayout {// Overwrite must call the super method [super prepareLayout];if([self.collectionView numberOfItemsInSection:0] == PageCount && self.attrsArray.count > PageCount) { [self.attrsArray removeAllObjects]; [self.columnHeights removeAllObjects]; } // When the column height array is empty, the first row is computed, with the base height of each column plus the top value of the collection's borderif(! self.columnHeights.count) {for(NSInteger i = 0; i < self.columnCount; i++) { [self.columnHeights addObject:@(self.edgeInsets.top)]; }} // Iterate over all cells and calculate the layout of all cellsfor (NSInteger i = self.attrsArray.count; i < [self.collectionView numberOfItemsInSection:0]; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; / / layout attributes and add the result to the layout attribute in the array [self. AttrsArray addObject: [self layoutAttributesForItemAtIndexPath: indexPath]]. }} // Return the layout property, A UICollectionViewLayoutAttributes array of objects - (NSArray < UICollectionViewLayoutAttributes * > *)layoutAttributesForElementsInRect:(CGRect)rect {returnself.attrsArray; } / / return collectionView ContentSize - (CGSize collectionViewContentSize) {/ / collectionView ContentSize height is equal to the largest of all the columns height value CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];for (NSInteger i = 1; i < self.columnCount; i++) {
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];
        if(maxColumnHeight < columnHeight) { maxColumnHeight = columnHeight; }}return CGSizeMake(0, maxColumnHeight + self.edgeInsets.bottom);
}
Copy the code

ColumnHeights save Saves the total height of each column, the width and height of the cell, and is calculated using random number + no double magnification + correction.

/ / calculate the layout attribute - (UICollectionViewLayoutAttributes *) layoutAttributesForItemAtIndexPath: (indexPath NSIndexPath *) { UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; / / cell width CGFloat w = (self. CollectionView. Frame. The size, width - self. EdgeInsets. Left - self. EdgeInsets. Right... self.columnMargin * (self.columnCount - 1)) / self.columnCount; / / the height of the cell JKRImageModel * shop = [self. The delegate modelWithIndexPath: indexPath]; CGFloat h = shop.height / shop.width * w; NSInteger destColumn = 0; CGFloat minColumnHeight = [self.columnheights [0] doubleValue]; CGFloat minColumnHeight = [self.columnheights [0] doubleValue]; // Get the minimum column heightfor (NSInteger i = 1; i < self.columnCount; i++) {
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];
        if(minColumnHeight > columnHeight) { minColumnHeight = columnHeight; destColumn = i; Self.edgeinset. left + destColumn * (w + self.columnMargin); // Calculate cell y CGFloat y = minColumnHeight;if(y ! = self.edgeInsets.top) { y += self.rowMargin; } // Determine whether to zoom inifDestColumn < self.columnCount - 1 Arc4random () % 100 > 33 // 33% chance not to enlarge && [self.columnHeights[destColumn] doubleValue] == [self.columnHeights[destColumn + 1] doubleValue] // Align the top of the current column with the top of the next column. DestColumn) {_noneDoubleTime = 0; _lastDoubleIndex = destColumn; // Redefine the current cell layout: width *2, height *2 attrs.frame = CGRectMake(x, y, w *2 + self.columnMargin, h *2 + self.rowMargin); self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame)); self.columnHeights[destColumn + 1] = @(CGRectGetMaxY(attrs.frame)); }else{// Normal cell layoutif(self. columnheights. count > destColumn + 1 && ABS(y + h - [self.columnHeights[destColumn + 1] doubleValue]) < h * 0.2) { // The height deviation between the current cell and the previous column is no more than 10% of the maximum cell height. Attrs. frame = CGRectMake(x, y, w, [self.columnHeights[destColumn + 1] doubleValue] -y); }else ifDestColumn >= 1 && ABS(y + h - [self.columnHeights[destcolumn-1] doubleValue]) < h * 0.2) {// The height difference between the current cell and the height listed above is no more than 10% of the maximum cell height. Attrs. frame = CGRectMake(x, y, w, [self.columnHeights[destcolumn-1] doubleValue] -y); }else{ attrs.frame = CGRectMake(x, y, w, h); Self. columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));} // The height of the current column is the maximum Y value of the current cell. _noneDoubleTime += 1; } // Return the computed layoutreturn attrs;
}
Copy the code

3, the source code

Click to view the source code.