UICollectionView

⼀、什么是集合视图
 
在iOS6.0之后,苹果推出了⼀个新的继承于UIScrollView的⼀个视 图,UICollectionView,也被称之为集合视图。和UITableView共同作为 在开发中⾮常常⽤的两个视图,常常作为项⽬的主界⾯出现。
 
⼆、创建UICollectionView
 
UICollection的实现跟tableView不⼀样的地⽅在于Item的布局 稍微复杂⼀点,需要⽤UICollectionViewLayout类来描述视图的 布局。我们在项⽬中常⽤的是系统提供的 UICollectionViewFlowLayout类,也可以⾃定义 UICollectionViewLayout。
 
//创建一个布局对象,采用系统布局类UICollectionViewFlowLayout
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];

    layout.itemSize = CGSizeMake(120, 200);
    layout.minimumLineSpacing = 0;
    layout.minimumInteritemSpacing = 7.5;
    layout.sectionInset = UIEdgeInsetsMake(50, 0, 0, 0);
  
    //设置最小的行间距
    layout.minimumLineSpacing = 20;
   
    //设置item与item之间的间距
    layout.minimumInteritemSpacing = 10;
   
    //设置集合视图的分区间隔
    layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
 
    //设置集合视图的滑动方向
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
   
    CGFloat totalWidth = self.view.frame.size.width;

    //设置每一个item的尺寸大小
    layout.itemSize = CGSizeMake((totalWidth - 40) / 3, 80);
   
    layout.headerReferenceSize = CGSizeMake(totalWidth, 40);
   
    //集合视图的创建,必须指定布局,如果没有布局,显示不了任何东西。
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];
   
    collectionView.dataSource = self;//数据源
    collectionView.delegate = self;//代理
   
//    collectionView.backgroundColor = [UIColor redColor];
   
    //集合视图如果想要显示内容,必须将cell进行注册
    [collectionView registerClass:[YourCollectionViewCell class] forCellWithReuseIdentifier:kStr];
   
    //集合视图如果想要分区头视图显示,必须注册增广视图
    [collectionView registerClass:[YourCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
   
    [self.view addSubview:collectionView];
 
UICollectionView和UITableView⼀样,也需要遵守两个代理协 议:UICollectionViewDelegate和UICollectionViewDataSource。
当遵守了这两个代理协议之后,UICollectionView才可以正常 的显⽰。
@interface ViewController : UIViewController<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@end
 
 
UICollectionView和UITableView⼀样有两个必须实现的代理⽅法。
返回多少个Item; 
指定每个item的样式。
 
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 6;
}

// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
   
    YourCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kStr forIndexPath:indexPath];
   
    cell.contentView.backgroundColor = [UIColor colorWithRed:arc4random() % 256 / 255.0 green:arc4random() % 256 / 255.0 blue:arc4random() % 256 / 255.0 alpha:1.0];
    cell.numberLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row];
   
    return cell;
}
 
选择实现的代理方法
 
//设置分区个数 返回有多少个分区
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 10;
}
 
//item点击之后触发的方法
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"%ld %ld",indexPath.section, indexPath.row);
}
 
UICollectionView不能像UITableView⼀样直接指定头部和尾部视 图,需要注册使⽤,最⼤的好处是添加了重⽤机制。
//返回增广视图,也就是集合视图的头视图或者尾视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    YourCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header" forIndexPath:indexPath];
    view.headerLabel.text = [NSString stringWithFormat:@"当前分区为:%ld",indexPath.section];
   
    view.backgroundColor = [UIColor yellowColor];
    return view;
}
 
三、布局协议
 
布局协议:UICollectionViewDelegateFlowLayout,它是对 UICollectionViewDelegate的扩展
 
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake((kWidth - 40) / 3, 100);
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(0, 0, 0, 0);
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
    return 20;
}


- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
    return 20;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    return CGSizeMake(kWidth, 40);
}
 
四、⾃定义UICollectionViewLayout
 
系统给我们提供的 UICollectionViewFlowLayout布局类不 能实现瀑布流的效果,如果我们想实现 瀑布流的效果,需要⾃定义⼀个 UICollectionViewLayout类,实现瀑布 流效果。
 
我们需要⼀个图⽚⼤⼩和图⽚地址的Json数据,和SDWebImage 图⽚加载的第三⽅⼯具。
 
第一步:创建⼀个继承于UICollectionViewLayout的类,声明⼀个代理协议,并声明协议⽅法。
 
@protocol WaterFlowLayoutDelegate <NSObject>

//关键方法,此方法的作用是返回每一个item的size大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;

@end
 
@interface WaterFlowLayout : UICollectionViewLayout
 
@property (nonatomic, assign) id<WaterFlowLayoutDelegate>delegate;
 
第二步:
 
在接⼝⽂件中声明需要提供给外界使⽤的属性。
 
//瀑布流一共多少列
@property (nonatomic, assign) NSUInteger numberOfColumn;
// item的⼤⼩
@property (nonatomic, assign) CGSize itemSize;
// 内边距
@property (nonatomic, assign) UIEdgeInsets sectionInsets;
// item的间距
@property (nonatomic, assign) CGFloat insertItemSpacing;
// 代理⼈
@property (nonatomic, assign) id<WaterFlowLayoutDelegate>delegate;
 
@end
 
第三步:
 
在实现⽂件中声明⼀些不需要提供给外界的⽅法和属性。
 
//  WaterFlowLayout.m
 
@interface WaterFlowLayout ()

// 获取item的总数量
@property (nonatomic, assign) NSUInteger numberOfItems;
//存放每一列的高度
@property (nonatomic, retain) NSMutableArray *columnHeightsArray;
//存放 每一个item的 属性 包含 frame以及下标(x,y,w,h)
@property (nonatomic, retain) NSMutableArray *attributesArray;
//保存每个Item的X值
@property (nonatomic, assign) CGFloat detalX;
//保存每个Item的Y值
@property (nonatomic, assign) CGFloat detalY;
//记录最短列
@property (nonatomic, assign) NSInteger shortestIndex;
//获取最长列的索引
- (NSInteger)p_indexForLongestColumn;
//获取最短列的索引
- (NSInteger)p_indexForShortestColumn;

@end
 
第四步:
 
数据源的懒加载⽅法
 
//懒加载
- (NSMutableArray *)columnHeightsArray {
    if (!_columnHeightsArray) {
        self.columnHeightsArray = [NSMutableArray array];
    }
    return _columnHeightsArray;
}
- (NSMutableArray *)attributesArray {
    if (!_attributesArray) {
        self.attributesArray = [NSMutableArray array];
    }
    return _attributesArray;
}
 
第五步:
 
//获取最小高度的方法
- (CGFloat)minHeight
{
    CGFloat min = 100000;
    for (NSNumber *height in _columnHeightsArray) {
        CGFloat h = [height floatValue];
        if (min > h) {
            min = h;
        }
    }
    return min;
}

//获取最大值
- (CGFloat)maxHeight
{
    CGFloat max = 0;
    for (NSNumber *height in _columnHeightsArray) {
        CGFloat h = [height floatValue];
        if (max < h) {
            max = h;
        }
    }
    return max;
}
 
 
第六步:
 
获取索引
 
//获取最短列索引
- (NSUInteger) p_indexForShortestColumn
{
    //记录索引
    NSUInteger index = 0;
    for (int i = 0; i < [_columnHeightsArray count]; i ++) {
        //获取当前高度
        CGFloat height = [_columnHeightsArray[i] floatValue];
        if (height == [self minHeight]) {
            index = i;
            return index;
        }
    }
    return index;
}
 
//获取最长列的索引
- (NSInteger)p_indexForLongestColumn {
    //记录哪一列最长
    NSInteger longestIndex = 0;
    for (int i = 0; self.numberOfColumn; i ++) {
        //获取高度
        CGFloat currentHeight = [self.columnHeightsArray[i] floatValue];
        if (currentHeight == [self maxHeight]) {
            longestIndex = i;
        }
    }
    return longestIndex;
}
 
第七步:
 
//重写父类的布局方法
- (void)prepareLayout
{
    [super prepareLayout];
   
    _attributesArray = [[NSMutableArray alloc] init];
   
    _columnHeightsArray = [[NSMutableArray alloc] initWithCapacity:self.numberOfColumn];
   
    //给列高数组里面的对象赋初值
    for (int i = 0; i < self.numberOfColumn; i ++) {
        [_columnHeightsArray addObject:@0.0];
    }
   
    CGFloat totalWidth = self.collectionView.frame.size.width;
   
    //创建 每个item frame中的x、y
    CGFloat x = 0;
    CGFloat y = 0;
   
    NSUInteger itemCount = [self.collectionView numberOfItemsInSection:0];
   
    for (int i = 0; i < itemCount; i ++) {
        //得到集合视图中 列间隙的个数
        NSUInteger numberOfSpace = self.numberOfColumn - 1;
       
        //代理对象执行代理方法,得到 item之间的间隙大小
        CGFloat spaceWidth = [_delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:0];
       
        //求每列的宽度,也就是每个item的width
        CGFloat width = (totalWidth - spaceWidth * numberOfSpace) / self.numberOfColumn;
       
       
        //获取每一个itemSize的大小
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
       
        //数据中原始图片大小
        CGSize imageSize = [_delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
   
        //通过 约分公式得到固定宽之后的高度是多少
        CGFloat height = width * imageSize.height / imageSize.width;
       
       
        UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
       
        //记录每一个item的大小和位置
        attribute.frame = CGRectMake(x, y, width, height);
       
        //数组保存每个item的位置信息
        [_attributesArray addObject:attribute];
       
        NSLog(@"item = %d",i);
        NSLog(@"x = %.2f y = %.2f width = %.2f height = %.2f",x,y,width,height);
       
        //求列高最小的那一列的下标
        NSUInteger minHeightIndex = [self indexOfMinHeight];
       
        //求出最小列的高度
        CGFloat minHeight = [_columnHeightsArray[minHeightIndex] floatValue];
       
        //求出行高
        CGFloat lineHeight = [_delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:0];
       
        //上一次总的列高 加上 行高 加上新加上的item的height,才是现在这一列的总高度
        //minHeight为最小列现在的高度
        //lineHeight为行间距
        //height为新加的item的高
        _columnHeightsArray[minHeightIndex] = [NSNumber numberWithFloat:minHeight + lineHeight + height];
       
        //重新算最小列高的下标
        minHeightIndex = [self indexOfMinHeight];
       
        //算下一次新加的item的x和y值
        x = (spaceWidth + width) * minHeightIndex;
       
        y = [self minHeight];
    }
}

//重写这个方法,可以返回集合视图的总高度
- (CGSize)collectionViewContentSize
{
    return CGSizeMake(self.collectionView.frame.size.width, [self maxHeight]);
}

//这个方法不写 集合视图显示不出来,这个方法是将保存的每个item的信息告诉集合视图,进行显示。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return _attributesArray;
}
 
 
原文地址:https://www.cnblogs.com/Walking-Jin/p/5211151.html