自定義UICollectionLayout佈局 —— UIKit之學習UICollectionView記錄一《瀑布流》

1、思路數組

思路一:比較每一行全部列的cell的高度,從上到下(也就是從第一行開始),從最短的開始計算,(記錄下b的高度和索引,從開始計算,依次類推)佈局

思路二:設置上、下、左、右間距和行間距、列間距及列數。ui

思路三:實現的重要的方法。atom

2、代碼先行。spa

 1.自定義layout類。3d

//入口
#import <UIKit/UIKit.h>

@protocol STRWaterLayoutDelegate;

@interface STRWaterLayout : UICollectionViewLayout

@property (nonatomic, weak)id<STRWaterLayoutDelegate>delegate;


@end
@protocol STRWaterLayoutDelegate <NSObject>

@required
- (CGFloat)waterLayout:(STRWaterLayout *)waterLayout layoutHeightAtindexPath:(NSIndexPath *)indexpath layoutItemWidth:(CGFloat)width;
@optional
- (UIEdgeInsets)edgeInsetsWithWaterLayout:(STRWaterLayout *)waterLayout;
- (CGFloat)columsNumberWithWaterLayout:(STRWaterLayout *)waterLayout;
- (CGFloat)rowsMarginWithWaterLayout:(STRWaterLayout *)waterLayout;
- (CGFloat)columsMarginWithWaterLaout:(STRWaterLayout *)waterLayout;
@end
//出口
#import "STRWaterLayout.h"
static const UIEdgeInsets layoutEdgeInsets = {10, 10, 10, 10}; //上、下、左、右的間距
static const CGFloat   columsMar = 10; //列間距
static const CGFloat   rowMar = 10;   //行間距
static const NSInteger columsNums = 3; //列數




@interface STRWaterLayout()

/**每一個cell的高度等信息*/
@property(nonatomic, strong)NSMutableArray   *columMinGapArray;
/**全部佈局信息的數組*/
@property(nonatomic, strong)NSMutableArray *layoutAttributesArray;
/**佈局的最終的高度*/
@property(nonatomic, assign)CGFloat  contentHeight;


- (UIEdgeInsets)edgeInsets; //上、下、左、右的間距
- (CGFloat)columsNumber;   //列數
- (CGFloat)rowMargin;      //行間距
- (CGFloat)columsMargin;   //列間距
@end
@implementation STRWaterLayout


#pragma mark --- delegate methods

- (UIEdgeInsets)edgeInsets{
    
    if (self.delegate && [self.delegate respondsToSelector:@selector(edgeInsetsWithWaterLayout:)]){
        
        return [self.delegate edgeInsetsWithWaterLayout:self];
    }else{
        return layoutEdgeInsets;
    }
}
- (CGFloat)columsNumber{
    
    if(self.delegate && [self.delegate respondsToSelector:@selector(columsNumberWithWaterLayout:)]){
        return [self.delegate columsNumberWithWaterLayout:self];
    }else{
        return columsNums;
    }
}
- (CGFloat)columsMargin{
    if(self.delegate && [self.delegate respondsToSelector:@selector(columsMarginWithWaterLaout:)]){
        return [self.delegate columsMarginWithWaterLaout:self];
    }else{
        return columsMar;
    }
}
- (CGFloat)rowMargin{
    if(self.delegate && [self.delegate respondsToSelector:@selector(rowsMarginWithWaterLayout:)]){
        return [self.delegate rowsMarginWithWaterLayout:self];
    }else{
        return rowMar;
    }
}
#pragma mark --- private methods

/**在從新佈局時會依次調用這四個方法*/

/**每次從新佈局時都會調用它*/
- (void)prepareLayout{
    [super prepareLayout];
    self.contentHeight = 0;
    [self.columMinGapArray removeAllObjects];
    [self.layoutAttributesArray removeAllObjects];
    for (NSInteger i = 0; i< [self columsNumber]; i++) { //遍歷全部的列數
        [self.columMinGapArray addObject: @([self edgeInsets].top)];
    }
    //當前只有一組,因此這麼處理
    NSInteger numbers = [self.collectionView numberOfItemsInSection:0];
    //處理全部的佈局數據
    for (NSInteger y = 0; y <numbers;y++) {
        //calls layouts methods,調用佈局的方法
        UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:y inSection:0]];
        //再把算出來的佈局加到佈局數組中。
        [self.layoutAttributesArray addObject:layoutAttributes];
    }
}
/**返回橫向滾動或縱向滾動的contentSize*/
- (CGSize)collectionViewContentSize{
    return  CGSizeMake(self.collectionView.frame.size.width, self.contentHeight+[self edgeInsets].bottom);
}
/**返回全部UICollectionViewLayoutAttributes的屬性的數組*/
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    return self.layoutAttributesArray;
}
/**返回每一個indexPath的佈局UICollectionViewLayoutAttributes*/
// 1.首先經過indexPath取出每一個collectionViewLayout的佈局(uicollectionlayoutAttributes).
// 2.從存儲每一個cell的高度的數組中取出第一個cell的高度,而後遍歷,存儲最小高度的cell,並記下它的索引,而後在這個索引下的cell增長下一個cell,而後再遍歷看看哪一個cell的高度最低,再把它的高度和索引記下來,增長下下個cell,依次類推。
// 3.由於佈局的寬度和x,y能夠本身設定(也就是能控制),不能控制的是佈局的高度,須要從外部傳進來,由於須要等比例的,因此須要傳當前indexPath的佈局的寬度,而後外面傳的時候用cell的實際寬度*實際高度/當前佈局的寬度。
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    
    UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    CGFloat minContentHeight = [self.columMinGapArray[0] doubleValue];
    NSInteger minIndex = 0;
    for (NSInteger i = 1; i < self.columMinGapArray.count; i++) {
        if (minContentHeight > [self.columMinGapArray[i] doubleValue]) {
            minContentHeight = [self.columMinGapArray[i] doubleValue];
            minIndex = i;
        }
    }
    CGFloat w = (self.collectionView.frame.size.width - [self edgeInsets].left - [self edgeInsets].right - ([self columsNumber]-1) * [self columsMargin])/[self columsNumber];
    
    CGFloat y = (minContentHeight == [self edgeInsets].top  ? minContentHeight : minContentHeight + [self rowMargin]);
    
    CGFloat x = [self edgeInsets].left + minIndex *(w+[self columsMargin]);
    
    CGFloat h = [self.delegate waterLayout:self layoutHeightAtindexPath:indexPath layoutItemWidth:w];
    
    layoutAttributes.frame = CGRectMake(x, y, w, h);
    self.columMinGapArray[minIndex] = @(CGRectGetMaxY(layoutAttributes.frame));
    
    CGFloat contentHeight = [self.columMinGapArray[minIndex] doubleValue];
    if (self.contentHeight < contentHeight) {
        self.contentHeight = contentHeight;
    }
    return layoutAttributes;
}

#pragma mark --- getters and setters 
- (NSMutableArray *)columMinGapArray{
    if (_columMinGapArray == nil) {
        
        _columMinGapArray = [NSMutableArray array];
    }
    return _columMinGapArray;
}
- (NSMutableArray *)layoutAttributesArray{
    if (_layoutAttributesArray == nil) {
        _layoutAttributesArray = [NSMutableArray array];
    }
    return _layoutAttributesArray;
}
@end

2.設置collectionView.代理

 1).注意事項1,須要設置佈局,上面建立的layout佈局。code

 2).注意事項2,須要設置代理和數據源,而後把必須實現的方法實現一下。orm

 3).注意事項3,註冊cell有兩種形式,一個是xib,一個是自定義的cell類,自定義或xib的cell類必定是設置collectionViewcell方法中的cell對象。對象

- (UICollectionView *)collectionView{
    if (!_collectionView) {
        
        STRWaterLayout *layout = [[STRWaterLayout alloc] init];
        layout.delegate = self;
        UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width,self.view.bounds.size.height) collectionViewLayout:layout];
        collectionView.delegate = self;
        collectionView.dataSource = self;
        collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(headRefresh)];
        collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(foorRefresh)];
        
        [collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:customCell];
        [self.view addSubview:collectionView];
        self.collectionView = collectionView;
    }
    return _collectionView;
}

3.實現collectionView的數據源方法或代理方法。

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return self.dataArray.count; //存儲的是佈局模型數據,有寬度、高度(最基本的)及其它對象數據。
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- ( UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    
    CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:customCell forIndexPath:indexPath];
    cell.layoutModel = self.dataArray[indexPath.row];
       return cell;
}

4.獲取數據。(我這裏是用的plist,這個能夠根據項目來設置,這裏是舉例。)

- (NSMutableArray *)dataArray{
    if (!_dataArray) {
        
        NSArray *data = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"1" ofType:@"plist"]];
        NSMutableArray *dataA = [NSMutableArray array];
        for (NSDictionary *dic in data) {
            STRWaterLayoutModel *layoutModel = [STRWaterLayoutModel getCollectionModel:dic];
            [dataA addObject:layoutModel];
        }
        _dataArray = dataA;
    
    }
    return _dataArray;
}

5.實現佈局layout的代理,傳入當前cell的高度和間距。

- (CGFloat)waterLayout:(STRWaterLayout *)waterLayout layoutHeightAtindexPath:(NSIndexPath *)indexpath layoutItemWidth:(CGFloat)width{
    STRWaterLayoutModel *waterLayoutModel = [self.dataArray objectAtIndex:indexpath.row];
    
//        return  width * [waterLayoutModel.h doubleValue] / [waterLayoutModel.w doubleValue];
    return [waterLayoutModel.h doubleValue];
}
- (CGFloat)columsNumberWithWaterLayout:(STRWaterLayout *)waterLayout{
    return 3;
    
}
- (CGFloat)columsMarginWithWaterLaout:(STRWaterLayout *)waterLayout{
    return 10;
}
- (CGFloat)rowsMarginWithWaterLayout:(STRWaterLayout *)waterLayout{
    return 10;
}
- (UIEdgeInsets)edgeInsetsWithWaterLayout:(STRWaterLayout *)waterLayout{
    return  UIEdgeInsetsMake(10,10, 10, 10);
}

3、總結。

    1.經過上面瀑布流的核心思想。

    2.建立collectionView的一些注意事項。

    3.給某個類傳數據時,能夠用代理,這纔是正兒八經遵照MVC的思想,後期改動的話,好擴展,若是用屬性的話,後期會很麻煩。

    4.代碼地址:待後期上傳。

相關文章
相關標籤/搜索