Before reading this article, hopefully you have some understanding of collectionView. #### i. Customizing layout is similar to implementing the function of TableView to customize cells. The idea of waterfall flow is to put the next item in the shortest column. So the attributes we need are column spacing, row spacing, margins, and an array that holds the attributes of the item.

@property (nonatomic, assign) NSInteger columnCount; // columnSpacing @property (nonatomic, assign) NSInteger columnSpacing; @property (nonatomic, assign) NSInteger rowSpacing; @property (nonatomic, assign) uiedGeInSet sectionInset; @property (nonatomic, strong) NSMutableDictionary *maxYDic; @property (nonatomic, strong) NSMutableArray *attributesArray; / / agent, used to calculate the height of the item @ property (nonatomic, weak) id < XRWaterfallLayoutDelegate > delegate;Copy the code

Next, we need to rewrite the four layout system methods ###### (1) – (void)prepareLayout method

- (void)prepareLayout {[super prepareLayout]; // Initialize the dictionary with key pairs as many columns as possible. Key is the column, value is the maximum y value of the column, and initial value is the upper inner marginfor(int i = 0; i < self.columnCount; i++) { self.maxYDic[@(i)] = @(self.sectionInset.top); NSInteger itemCount = [self.collectionView numberOfItemsInSection:0]; [self.attributesArray removeAllObjects]; // Create an Attributes for each item and store it in an arrayfor (int i = 0; i < itemCount; i++) {
        UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; [self.attributesArray addObject:attributes]; }}Copy the code

# # # # # # (2) – (CGSize) collectionViewContentSize method

/ / calculate the contentSize collectionView - (CGSize) collectionViewContentSize {__block NSNumber * maxIndex = @ 0; / / traverse the dictionary to find the longest that a column [self. MaxYDic enumerateKeysAndObjectsUsingBlock: ^ (NSNumber * key, NSNumber * obj, BOOL * stop) {if ([self.maxYDic[maxIndex] floatValue] < obj.floatValue) { maxIndex = key; }}]; Height is equal to the maximum y value of the longest column + the bottom inner marginreturn CGSizeMake(0, [self.maxYDic[maxIndex] floatValue] + self.sectionInset.bottom);
}
Copy the code

# # # # # # (3) – (UICollectionViewLayoutAttributes *) layoutAttributesForItemAtIndexPath indexPath method: (NSIndexPath *) The method is used to set the attributes of each item. In this case, we simply set the attributes. Frame of each item. ###### Item width = (width of collectionView – inner margins and column margins)/number of columns

CGFloat collectionViewWidth = self.collectionView.frame.size.width; Self.sectioninset. left: left self.sectionInset.right: right //(self.columncount - 1) * columnSpacing: All column margins in a row CGFloat itemWidth = (collectionViewWidth - self.sectionInSet. Left - self.sectionInset. Right - (self.columnCount - 1) * self.columnSpacing) / self.columnCount;Copy the code

###### the y value of the item is equal to the maximum y value of the shortest column plus the row spacing, and the x value is equal to the left margin + (item width + column spacing) times minColumn

__block NSNumber *minIndex = @0; [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL *stop) {if ([self.maxYDic[minIndex] floatValue] > obj.floatValue) { minIndex = key; }}]; CGFloat itemX = self.sectionInSet. left + (self.columnSpacing + itemWidth) * minindex. integerValue; CGFloat itemY = [self.maxydic [minIndex]floatValue] + self.rowSpacing;
Copy the code

Next comes the height of the item. We should scale the height of the item according to the original size of the image and the calculated width. But in layout class, we can’t get the image, so we can define a block property, or a proxy, and let the outside world calculate it and return it to us. We need to pass the width of the item and the indexPath to the outside world

@required // Proxy method to calculate the height of item, Pass the height and indexPath of the item to the outside world - (CGFloat)waterfallLayout:(XRWaterfallLayout *)waterfallLayout itemHeightForWidth:(CGFloat)itemWidth atIndexPath:(NSIndexPath *)indexPath; @endCopy the code

See the detailed code 👇 :

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { / / to indexPath access the attributes of the item attributes UICollectionViewLayoutAttributes * = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; / / get the width of collectionView CGFloat collectionViewWidth = self. CollectionView. Frame. The size. The width; CGFloat itemWidth = (EctionViewWidth - self.sectionInSet.left -) self.sectionInset.right - (self.columnCount - 1) * self.columnSpacing) / self.columnCount; CGFloat itemHeight = 0; // Get the height of item, calculated from the outsideif([self.delegate respondsToSelector:@selector(waterfallLayout:itemHeightForWidth:atIndexPath:)]) itemHeight = [self.delegate waterfallLayout:self itemHeightForWidth:itemWidth atIndexPath:indexPath]; __block NSNumber *minIndex = @0; [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL *stop) {if ([self.maxYDic[minIndex] floatValue] > obj.floatValue) { minIndex = key; }}]; CGFloat itemX = self.sectionInSet. left + (self.columnSpacing + itemWidth) * minindex. integerValue; CGFloat itemY = [self.maxydic [minIndex]floatValue] + self.rowSpacing; // Set attributes to frame Attributes. Frame = CGRectMake(itemX, itemY, itemWidth, itemHeight); // Update the maximum y value in the dictionary self.maxydic [minIndex] = @(CGRectGetMaxY(attributes.frame));return attributes;
}
Copy the code

# # # # # # (4) – (NSArray *) layoutAttributesForElementsInRect: (CGRect) the rect method returns attributesArray directly is then

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
return self.attributesArray;
}
Copy the code

2. Use in viewController:

(1) Create a waterfall stream
/ / create the waterfall flow layout XRWaterfallLayout * waterfall = [XRWaterfallLayout waterFallLayoutWithColumnCount: 3]; // Set the value of each attribute to waterfall.rowSpacing = 10; waterfall.columnSpacing = 10; waterfall.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); // Implement the delegate method waterfall.delegate = self; / / create collectionView self. CollectionView = [[UICollectionView alloc] initWithFrame: self. The bounds collectionViewLayout:waterfall]; self.collectionView.backgroundColor = [UIColor whiteColor]; [self.collectionView registerNib:[UINib nibWithNibName:@"XRCollectionViewCell" bundle:nil] forCellWithReuseIdentifier:@"cell"];
   self.collectionView.dataSource = self;
   [self.view addSubview:self.collectionView];
Copy the code
(2) Implement the proxy method to calculate the height of item
// calculate the height of each item based on its width and indexPath - (CGFloat)waterfallLayout:(XRWaterfallLayout *)waterfallLayout ItemHeightForWidth :(CGFloat)itemWidth atIndexPath:(NSIndexPath *)indexPath { XRImage *image = self.images[indexpath.item];return image.imageH / image.imageW * itemWidth;
}
Copy the code

At this point, I have completed the simple waterfall flow. Of course, I read the original blogger’s article and basically copied it, so I was impressed by it. Besides, the original blogger’s original code also contains lazy loading, rewriting constructors, rewriting setter methods, etc., but after all, God Is high and cold, 😄😄😄 (once again 🙏 original blogger)