iOS——UICollectionView

UICollectionView基础:

一, UICollectionView简介:

UICollectionView是iOS 6.0之后引入的一种UI控件,类似于tableView有相似的代理方法,但UIColletionView的功能更为强大,可以实现瀑布流,根据开发者的喜好去自定义布局。

二, 简单的使用UICollectionView

下面先实现一个简单的九宫格布局来介绍此控件的一些基本属性。

    类似于使用tableView,需要实现两个协议。
    分别为1UICollectionViewDelegate以及UICollectionViewDataSource.
@interface ViewController : UIViewController <
UICollectionViewDelegate,
UICollectionViewDataSource
>
@property (nonatomic, strong) UICollectionView* collectionView;
@end

2.设置布局,代理,设置item的大小
区别于tableView,UICollectionView是根据UICollectionViewFlowLayout设置布局的。该布局有两种形式:
UICollectionViewScrollDirectionVertical:垂直流布局。所谓垂直流布局,即item从行开始布局,第一行满后从第二行开始。

UICollectionViewScrollDirectionHorizontal:水平流布局,从列开始布局,第一列满后 从第二列开始。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    // 创建一个layout布局
    UICollectionViewFlowLayout* layout = [[UICollectionViewFlowLayout alloc]init];
    // 设置布局方向为垂直流布局
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
      // 水平布局
//    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
    // 设置每个item大小
    layout.itemSize = CGSizeMake(120, 120);
    _collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
    // 设置代理
    _collectionView.delegate = self;
    _collectionView.dataSource = self;
    // 注册item类型
    [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"test"];
    [self.view addSubview:_collectionView];
}

这里的item是需要注册从复用池获取cell的。
3.实现协议函数
类似于使用tableView这里要实现必须实现的函数

/返回分区个数
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
//返回每个分区的item个数
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 9;
}
//返回每个item
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"test" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 255 / 255.0 alpha:1];
    return cell;
}

在这里插入图片描述

三, UICollection的代理方法

1.UICollectionViewDataSource协议
这个协议主要用于collectionView相关数据的处理。
首先有两个必须实现的方法
设置每个分区的item个数:- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;.
设置返回每个item的属性:- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

``
剩下的方法是可以选择实现的
设置分区的个数:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
对头视图或者尾视图进行设置:
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
设置某个item是否可以被移动,返回NO则不能移动:

- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);

移动item的时候,会调用这个方法

- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;
2.UICollectionViewDelegate协议
这个协议是用来设置UICollectionView的一些功能以及逻辑,其所有方法都是可选实现的。
是否允许某个Item的高亮,返回NO,则不能进入高亮状态:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;

当item高亮时触发的方法:

- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;

结束高亮状态时触发的方法:

- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;

是否可以选中某个Item,返回NO,则不能选中:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;

是否可以取消选中某个Item:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;

已经选中某个item时触发的方法:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;

取消选中某个Item时触发的方法:

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;

将要加载某个Item时调用的方法:

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);

将要加载头尾视图时调用的方法:

- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);

已经展示某个Item时触发的方法:

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;

已经展示某个头尾视图时触发的方法:

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

这个方法设置是否展示长按菜单:

- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;

这个方法用于设置要展示的菜单选项:

- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;

这个方法用于实现点击菜单按钮后的触发方法:

- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;

使用UICollectionViewFlowLayout灵活布局

一些基本属性

上面我们实现了一个简单的九宫格布局,但日常的开发中显然不满足于仅仅使用九宫格,UICollectionViewFlowLayout是系统提供给我们一个封装好的流布局设置类,我们可以去设置layout的一些属性去得到更灵活的布局。

@property (nonatomic) CGFloat minimumLineSpacing:设置行与行之间的间距最小距离

@property (nonatomic) CGFloat minimumInteritemSpacing;:设置列与列之间的间距最小距离

@property (nonatomic) CGSize itemSize;:设置每个item的大小

@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);:设置每个Item的估计大小,一般不需要设置
@property (nonatomic) UICollectionViewScrollDirection scrollDirection;:设置布局格式,即上面提到的垂直和水平。
@property (nonatomic) CGSize headerReferenceSize;:设置头视图尺寸大小
@property (nonatomic) CGSize footerReferenceSize;:
``:设置尾视图尺寸大小
@property (nonatomic) UIEdgeInsets sectionInset;这个属性可以设置分区的偏移量
我们可以对上面九宫格的代码做一些修改,例如根据单双数改变item的尺寸,并且设置item的偏移量。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    UICollectionViewFlowLayout* testFlowLayout = [[UICollectionViewFlowLayout alloc] init];
    testFlowLayout.sectionInset = UIEdgeInsetsMake(20, 20, 20, 20);


    // 设置模式
    testFlowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
    _collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:testFlowLayout];
    _collectionView.delegate = self;
    _collectionView.dataSource = self;
    [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"test"];
    [self.view addSubview:_collectionView];
}
// 设置item大小
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    if (indexPath.row % 2 == 0) {
        return CGSizeMake(80, 80);
    }else{
        return CGSizeMake(120, 120);
    }
}
//代理相应方法
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 100;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"test" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255/255.0 blue:arc4random() % 255 / 255.0 alpha:1];
    return cell;
}


@end

效果如下:
在这里插入图片描述
通过设置偏移量可以和边界留出间隔。

MyLayout

创建类

UICollectionView强大的一个原因,就是我们可以制作属于我们的布局,在我们平常刷一些app时,刷新加载的图片大小可能是不确定的,这是使用系统自带的布局是有局限的,所以我们可以自己去创造一个MyLayout类去实现它。
创建一个MyLayout类继承UICollectionViewFlowLayout.

重写相关方法

UICollectionViewFlowLayout是一个专门用来管理collectionView布局的类,因此,collectionView在进行UI布局前,会通过这个类的对象获取相关的布局信息,FlowLayout类将这些布局信息全部存放在了一个数组中,在collectionView布局时,会调用FlowLayout类layoutAttributesForElementsInRect:方法来获取这个布局配置数组。因此,我们需要重写这个方法,返回我们自定义的配置数组,另外,FlowLayout类在进行布局之前,会调用prepareLayout方法,所以我们可以重写这个方法,在里面对我们的自定义配置数据进行一些设置。

//
//  MyLayout.m
//  自定义Flowlayout
//
//  Created by 差不多先生 on 2022/1/15.
//

#import "MyLayout.h"

@implementation MyLayout {
    // 配置数组
    NSMutableArray* _attributeAttay;
}

//数组的相关设置在这个方法中
//布局前的准备会调用这个方法
-(void)prepareLayout{
    _attributeAttay = [[NSMutableArray alloc] init];
    [super prepareLayout];
    //计算每一个item的宽度
    float WIDTH = ([UIScreen mainScreen].bounds.size.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing) / 2;
    
    //定义数组保存每一列的高度
    //这个数组的主要作用是保存每一列的总高度,这样在布局时,我们可以始终将下一个Item放在最短的列下面
    CGFloat colHight[2] = {self.sectionInset.top,self.sectionInset.bottom};
    //itemCount是外界传进来的item的个数 遍历来设置每一个item的布局
    for (int i = 0; i < _itemCount; i++) {
        //设置每个item的位置等相关属性
        NSIndexPath *index = [NSIndexPath indexPathForItem:i inSection:0];
        //创建一个布局属性类,通过indexPath来创建
        UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:index];
        //随机一个高度 在100——200之间
        CGFloat hight = arc4random() % 100 + 100;
        //哪一列高度小 则放到那一列下面
        //标记最短的列
        int width = 0;
        if (colHight[0] < colHight[1]) {
            //将新的item高度加入到短的一列
            colHight[0] = colHight[0]+ hight + self.minimumLineSpacing;
            width = 0;
        }else{
            colHight[1] = colHight[1] + hight + self.minimumLineSpacing;
            width = 1;
        }
        
        //设置item的位置
        attris.frame = CGRectMake(self.sectionInset.left + (self.minimumInteritemSpacing+WIDTH) * width, colHight[width] - hight - self.minimumLineSpacing, WIDTH, hight); 
        [_attributeAttay addObject:attris];
    }
    
    //设置itemSize来确保滑动范围的正确 这里是通过将所有的item高度平均化,计算出来的(以最高的列位标准)
    if (colHight[0] > colHight[1]) {
        self.itemSize = CGSizeMake(WIDTH, (colHight[0] - self.sectionInset.top) * 2 / _itemCount - self.minimumLineSpacing);
    }else{
          self.itemSize = CGSizeMake(WIDTH, (colHight[1] - self.sectionInset.top) * 2 / _itemCount-self.minimumLineSpacing);
    }
    
}
//这个方法中返回我们的布局数组
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    return _attributeAttay;
}
 
 
@end

配置完Layout以后和之前一样使用即可

//
//  ViewController.m
//  自定义Flowlayout
//
//  Created by 差不多先生 on 2022/1/15.
//

#import "ViewController.h"
#import "MyLayout.h"


@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    MyLayout * layout = [[MyLayout alloc]init];
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
    layout.itemCount = 100;
     UICollectionView * collect  = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:layout];
    collect.delegate = self;
    collect.dataSource = self;
    
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
  
    [self.view addSubview:collect];
    
    
}
 
 
 
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 100;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellid" forIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255 / 255.0 green:arc4random()%255 / 255.0 blue:arc4random()%255 / 255.0 alpha:1];
    return cell;
}

@end

效果如下:
在这里插入图片描述

一些可以配置的属性

//配置item的布局位置
@property (nonatomic) CGRect frame;
//配置item的中心
@property (nonatomic) CGPoint center;
//配置item的尺寸
@property (nonatomic) CGSize size;
//配置item的3D效果
@property (nonatomic) CATransform3D transform3D;
//配置item的bounds
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
//配置item的旋转
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
//配置item的alpha
@property (nonatomic) CGFloat alpha;
//配置item的z坐标
@property (nonatomic) NSInteger zIndex; // default is 0
//配置item的隐藏
@property (nonatomic, getter=isHidden) BOOL hidden; 
//item的indexpath
@property (nonatomic, strong) NSIndexPath *indexPath;
//获取item的类型
@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property (nonatomic, readonly, nullable) NSString *representedElementKind; 
 
//一些创建方法
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;


自定义布局实现环形布局

和上面的自定义高度类似,首先是重写prepareLayout,为布局做一些准备工作,使用collectionViewContentSize来设置内容的区域大小,最后使用layoutAttributesForElementsInRect方法来返回我们的布局信息字典

//
//  MyLayout.m
//  自定义Flowlayout
//
//  Created by 差不多先生 on 2022/1/15.
//

#import "MyLayout.h"

@implementation MyLayout
{
    NSMutableArray * _attributeAttay;
}
-(void)prepareLayout{
    [super prepareLayout];
    //获取item的个数
    _itemCount = (int)[self.collectionView numberOfItemsInSection:0];
    _attributeAttay = [[NSMutableArray alloc]init];
    //先设定大圆的半径 取长和宽最短的
    CGFloat radius = MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height) / 2;
    //计算圆心位置
    CGPoint center = CGPointMake(self.collectionView.frame.size.width / 2, self.collectionView.frame.size.height / 2);
    //设置每个item的大小
    for (int i = 0; i <_itemCount; i++) {
        UICollectionViewLayoutAttributes * attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        //设置item大小
        attris.size = CGSizeMake(100, 100);
        //计算每个item的圆心位置
        
        //计算每个item中心的坐标
        //算出的x y值还要减去item自身的半径大小
        float x = center.x + cosf(2 * M_PI / _itemCount * i) * (radius - 50);
        float y = center.y + sinf(2 * M_PI / _itemCount * i) * (radius - 50 );
     
        attris.center = CGPointMake(x, y);
        [_attributeAttay addObject:attris];
    }
    
   
    
}
//设置内容区域的大小
-(CGSize)collectionViewContentSize{
    return self.collectionView.frame.size;
}
//返回设置数组
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    return _attributeAttay;
}


 
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    MyLayout * layout = [[MyLayout alloc]init];
     UICollectionView * collect  = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:layout];
    collect.delegate = self;
    collect.dataSource = self;
    
    [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"test"];
    [self.view addSubview:collect];
}
 
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return 8;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewCell * cell  = [collectionView dequeueReusableCellWithReuseIdentifier:@"test" forIndexPath:indexPath];
    cell.layer.masksToBounds = YES;
    // 设置半径
    cell.layer.cornerRadius = 50;
    cell.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 255 / 255.0 alpha:1];
    return cell;
}

效果如下:
在这里插入图片描述

来源url
栏目