美文网首页程序员
IOS-自定义CollectionView的layout以及添加

IOS-自定义CollectionView的layout以及添加

作者: 温学振 | 来源:发表于2018-01-30 17:37 被阅读562次
  • 先看例子,再看知识点


    成型的collectionView
  • 在viewDidLoad里
- (void)viewDidLoad {
    [super viewDidLoad];
    //这里从本地获取数据存在数组self.Array里
    NSArray * shopsArray = [shopModel mj_objectArrayWithFilename:@"1.plist"];
    [self.Array addObjectsFromArray:shopsArray];
    
    //这是自定义瀑布流
    FlowLayout *flowlayout1 = [[FlowLayout alloc] init];
    //这个代理为了能够根据数组内图片的宽高等比缩放
    flowlayout1.delegate = self;
    self.mainView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) collectionViewLayout:flowlayout1];
    self.mainView.backgroundColor = [UIColor redColor];
    self.mainView.delegate = self;
    self.mainView.dataSource = self;
    self.mainView.scrollEnabled = YES;
    
    [self.view addSubview:self.mainView];
    //注册一个Cell
    [self.mainView registerClass:[CollectionCell class] forCellWithReuseIdentifier:@"CollectionCell"];
    //注册一个ReusableView,类型是UICollectionElementKindSectionHeader的(头部)
    [self.mainView registerClass:[headReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headReusableView"];
    //注册一个ReusableView,类型是UICollectionElementKindSectionFooter的(尾部)
//    [self.mainView registerClass:[footReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footReusableView"];
    
}
  • collectionView的代理
#pragma mark  设置CollectionView的组数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
#pragma mark  设置CollectionView每组所包含的个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return self.Array.count;
}
#pragma mark  设置CollectionView所展示出来的cell
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath];
    cell.model = self.Array[indexPath.item];
    return cell;
}
#pragma mark  定义每个UICollectionView的大小
//- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
//    return CGSizeMake(100, 100);
//}
#pragma mark  定义整个CollectionViewCell与整个View的间距
//- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
//    return UIEdgeInsetsMake(10, 10, 10, 10);
//}
#pragma mark  设置CollectionViewCell是否可以被点击
//- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
//    return YES;
//}
#pragma mark  点击CollectionView触发事件
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"---------------------");
}
#pragma mark headView大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
    return CGSizeMake(self.view.frame.size.width, 100);
}
//#pragma mark footView大小
//- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section{
//    return CGSizeMake(self.view.frame.size.width, 80);
//}
#pragma mark headView和footView都在这里判断
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
    headReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headReusableView" forIndexPath:indexPath];
//    footReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footReusableView" forIndexPath:indexPath];
//    [footerView.btn1 addTarget:self action:@selector(btn1Click) forControlEvents:UIControlEventTouchUpInside];
    [headerView.Btn1 addTarget:self action:@selector(Btn1Click) forControlEvents:UIControlEventTouchUpInside];
//    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        return headerView;
//    }else{
//        return footerView;
//    }
}
  • 自己定义的代理(为了计算等比宽高)
#pragma mark wxzFlowLayoutDelegate
- (CGFloat)WXZWaterFlow:(UICollectionViewFlowLayout *)waterFlow heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath{
    shopModel *model = self.Array[indexPath.item];
    return model.h * width / model.w ;
}
  • 在UICollectionViewFlowLayout.h中
#import <UIKit/UIKit.h>
@class UICollectionViewFlowLayout;

@protocol WXZWaterFlowDelegte <NSObject>
- (CGFloat)WXZWaterFlow:(UICollectionViewFlowLayout *)waterFlow heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath;
@end

@interface FlowLayout : UICollectionViewFlowLayout

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

@end
  • 在UICollectionViewFlowLayout.m中
#import "FlowLayout.h"

#define WXZCollectionW self.collectionView.frame.size.width

/** 每一行之间的间距 */
static const CGFloat  WXZDefaultRowMargin = 5;
/** 每一列之间的间距 */
static const CGFloat  WXZDefaultColumnMargin = 10;
/** 每一列之间的间距 top, left, bottom, right */
static const UIEdgeInsets  WXZDefaultInsets = {5, 15, 5, 5};
/** 默认的列数 */
static const int WXZDefaultColumsCount = 2;

@interface FlowLayout()
/** 每一列的最大Y值 */
@property (nonatomic, strong) NSMutableArray *columnMaxYs;
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;

@property (nonatomic, strong) NSArray *heightArray;

@end

@implementation FlowLayout
#pragma mark -lazy
- (NSMutableArray *)columnMaxYs
{
    if (!_columnMaxYs) {
        _columnMaxYs = [[NSMutableArray alloc] init];
    }
    return _columnMaxYs;
}

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [[NSMutableArray alloc] init];
    }
    return _attrsArray;
}

- (NSArray *)heightArray
{
    if (!_heightArray) {
        _heightArray = [[NSArray alloc] init];
        _heightArray = @[@85,@105,@115,@105];
    }
    return _heightArray;
}

#pragma mark - 实现内部的方法
/**
 * 决定了collectionView的contentSize。由于collectionView将item的布局任务委托给layout对象,那么滚动区域的大小对于它而言是不可知的。自定义的布局对象必须在这个方法里面计算出显示内容的大小,包括supplementaryView和decorationView在内。
 */
- (CGSize)collectionViewContentSize
{
    // 找出最长那一列的最大Y值
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // 取出第i列的最大Y值
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
        
        // 找出数组中的最大值
        if (destMaxY < columnMaxY) {
            destMaxY = columnMaxY;
        }
    }
    return CGSizeMake(0, destMaxY + WXZDefaultInsets.bottom);
}
/**
 * 系统在准备对item进行布局前会调用这个方法,我们重写这个方法之后可以在方法里面预先设置好需要用到的变量属性等。比如在瀑布流开始布局前,我们可以对存储瀑布流高度的数组进行初始化。有时我们还需要将布局属性对象进行存储,比如卡片动画式的定制,也可以在这个方法里面进行初始化数组。切记要调用[super prepareLayout];
 */
//
- (void)prepareLayout
{
    [super prepareLayout];
    
    // 重置每一列的最大Y值
    [self.columnMaxYs removeAllObjects];
    for (NSUInteger i = 0; i<WXZDefaultColumsCount; i++) {
        [self.columnMaxYs addObject:@(WXZDefaultInsets.top)];
    }
    
    // 计算所有cell的布局属性
    [self.attrsArray removeAllObjects];
    NSUInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSUInteger i = 0; i < count; ++i) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}
/**
 * 说明所有元素(比如cell、补充控件、装饰控件)的布局属性。个人觉得完成定制布局最核心的方法,没有之一。collectionView调用这个方法并将自身坐标系统中的矩形传过来,这个矩形代表着当前collectionView可视的范围。我们需要在这个方法里面返回一个包括UICollectionViewLayoutAttributes对象的数组,这个布局属性对象决定了当前显示的item的大小、层次、可视属性在内的布局属性。同时,这个方法还可以设置supplementaryView和decorationView的布局属性。合理使用这个方法的前提是不要随便返回所有的属性,除非这个view处在当前collectionView的可视范围内,又或者大量额外的计算造成的用户体验下降——你加班的原因。
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    //找到collectionVIew的头部headReusableView并且添加到数组里,这样子就能够显示出头部里
    [self.attrsArray addObjectsFromArray:[super layoutAttributesForElementsInRect:rect]];
    return self.attrsArray;
}

/**
 * 说明cell的布局属性,相当重要的方法。collectionView可能会为了某些特殊的item请求特殊的布局属性,我们可以在这个方法中创建并且返回特别定制的布局属性。根据传入的indexPath调用[UICollectionViewLayoutAttributes layoutAttributesWithIndexPath: ]方法来创建属性对象,然后设置创建好的属性,包括定制形变、位移等动画效果在内
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    /** 计算indexPath位置cell的布局属性 */
    
    // 水平方向上的总间距
    CGFloat xMargin = WXZDefaultInsets.left + WXZDefaultInsets.right + (WXZDefaultColumsCount - 1) * WXZDefaultColumnMargin;
    // cell的宽度
    
    CGFloat w = (WXZCollectionW - xMargin - 20) / WXZDefaultColumsCount;
    // cell的高度
    CGFloat h = [self.delegate WXZWaterFlow:self heightForWidth:w atIndexPath:indexPath];
    
    // 找出最短那一列的 列号 和 最大Y值  要判断第一个才+363
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    NSUInteger destColumn = 0;
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // 取出第i列的最大Y值
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
        
        // 找出数组中的最小值
        if (destMaxY > columnMaxY) {
            destMaxY = columnMaxY;
            destColumn = i;
        }
    }
    
    // cell的x值
    CGFloat x = WXZDefaultInsets.left + destColumn * (w + WXZDefaultColumnMargin);
    
    CGFloat y = destMaxY + WXZDefaultRowMargin;
    
    // cell的frame
    attrs.frame = CGRectMake(x, y, w, h);
    
    // cell的y值
    if (destMaxY==5) {
        //手动增加第一个cell的高,如果不增加 ,那么cell是从顶部开始 。会与头部相叠
        attrs.frame = CGRectMake(x, 120, w, h);
    }
    
    // 更新数组中的最大Y值
    self.columnMaxYs[destColumn] = @(CGRectGetMaxY(attrs.frame));
    
    return attrs;
}

//- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
当collectionView的bounds改变的时候,我们需要告诉collectionView是否需要重新计算布局属性,通过这个方法返回是否需要重新计算的结果。简单的返回YES会导致我们的布局在每一秒都在进行不断的重绘布局,造成额外的计算任务。
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    //    NSLog(@"%s",__func__);
    return NO;
}
@end
  • 你可以在CollectionView的头部headReusableView里添加任何东西 ,他继承自UIView,如果你的CollectionView的FlowLayout是系统自定义的,那么你就不需要再将自定义的headReusableView添加进FlowLayout的数组里了。系统已经将你想要的做好了。
  • CollectionView非常好用,建议多多的学习。有如下扩展:对每一张图片的介绍文字有多有少。那么我们的高度还是图片高度+字体所对应的大小

    扩展

相关文章

网友评论

    本文标题:IOS-自定义CollectionView的layout以及添加

    本文链接:https://www.haomeiwen.com/subject/pynjoxtx.html