【iOS】UICollectionView使用

使用UITableView作为表格来展示数据完全没有问题,但仍有许多局限性,对于一些更加复杂的布局样式,就有些力不从心了

比如,UITableView只允许表格每一行只能显示一个cell,而不能在一行中显示多个cell,对于这种更为复杂的布局需求,UICollectionView可以提供更好的支持,有着更大的灵活性和扩展性,其主要优势有以下几点:

  • 支持横向 + 纵向两个方向的布局
  • 更加灵活的布局方式、动画
  • 可以动态对布局进行重设(切换layout)

目录


UICollectionView的基础使用

作为升级版的UITableView,UICollectionView有许多与UITableView相似的点,可以通过对比UITableView总结来进行学习:

  • row ---> item:由于一行可以展示多个视图,row不能准确表达
  • - (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;:注册cell类型,并设置重用ID
  • - (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;:cell复用,与UITableViewCell的注册机制一样,每次调用这个方法时,如果复用池中没有可复用的cell(cell为空),会根据重用ID自动创建一个新的cell并返回

显示UICollectionView

下面展示一个最基本的CollectionView

objectivec 复制代码
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = [UIColor blueColor];
    
    //创建布局策略
    UICollectionViewFlowLayout* flowLayOut = [[UICollectionViewFlowLayout alloc] init];
    
    //第二个参数flowLayout用于生成UICollectionView的布局信息,后面会解释这个布局类
    UICollectionView* collectionView = [[UICollectionView alloc] initWithFrame: self.view.bounds collectionViewLayout: flowLayOut];
    collectionView.dataSource = self;
    
    //注册cell
    [collectionView registerClass: [UICollectionViewCell class] forCellWithReuseIdentifier: @"UICollectionViewCell"];
    [self.view addSubview: collectionView];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 21;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier: @"UICollectionViewCell" forIndexPath: indexPath];

    //cell的颜色
    cell.backgroundColor = [UIColor cyanColor];
    
    //cell默认是50X50的大小
    
    return cell;
}

UICollectionViewLayout布局策略(UICollectionViewLayoutAttributes)

layout管理多个attributes,一个cell就会对应一个布局信息attribute

UICollectionViewFlowLayout流式布局

作为一个生成布局信息(item的大小、位置、3D变换等)的抽象类,要实际使用需要继承,比如系统提供的继承于UICollectionViewLayout的一个流式布局类UICollectionViewFlowLayout,下面使用这个类来进行布局:

objectivec 复制代码
UICollectionViewFlowLayout* flowLayOut = [[UICollectionViewFlowLayout alloc] init];

//设置布局方向
flowLayOut.scrollDirection = UICollectionViewScrollDirectionVertical;
flowLayOut.minimumLineSpacing = 10;  //行间距
flowLayOut.minimumInteritemSpacing = 10;  //列间距

//设置每个item的尺寸
flowLayOut.itemSize = CGSizeMake(self.view.frame.size.width / 2 - 5, 300);
//    flowLayOut.itemSize = CGSizeMake(self.view.frame.size.width / 2 - 50, 300);

scrollDirection属性

该类的scrollDirection属性用于设置布局的方向,支持的布局方向枚举如下:

若将枚举值改为UICollectionViewScrollDirectionHorizontal,将会这样排列:

前者当一行满时,另起一行;后者当一列满时,另起一列

minimumInteritemSpacing最小列间距属性

系统通过minimumInteritemSpacing属性计算一行可以放多少个item,当发现放不下计算好的item个数时,为了撑满所在行,此值就会变大,比如:

objectivec 复制代码
flowLayOut.itemSize = CGSizeMake(self.view.frame.size.width / 2 - 12, 300);

minimumLineSpacing最小行间距同理

九宫格布局

UITableView类似,UICollectionView 不仅内部也有一套复用机制来对注册的cell进行复用,而且也是通过dataSourcedelegate协议来进行数据填充相关属性设置的:

objectivec 复制代码
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = [UIColor blueColor];
    
    UICollectionViewFlowLayout* flowLayOut = [[UICollectionViewFlowLayout alloc] init];
    flowLayOut.scrollDirection = UICollectionViewScrollDirectionVertical;
    
    CGFloat side = (self.view.bounds.size.width - 12) / 3;
    flowLayOut.minimumLineSpacing = 6;
    flowLayOut.minimumInteritemSpacing = 6;
    flowLayOut.itemSize = CGSizeMake(side, side);
    
    UICollectionView* collectionView = [[UICollectionView alloc] initWithFrame: self.view.bounds collectionViewLayout: flowLayOut];
    collectionView.dataSource = self;
    collectionView.delegate = self;
    
    [collectionView registerClass: [UICollectionViewCell class] forCellWithReuseIdentifier: @"UICollectionViewCell"];
    
    [self.view addSubview: collectionView];
}

//设置分区数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

//设置每个分区的
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 10;
}

//每条item上cell的UI展现
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier: @"UICollectionViewCell" forIndexPath: indexPath];
    
    //随机颜色
    cell.backgroundColor = [UIColor colorWithRed: arc4random() % 255 / 255.0 green: arc4random() % 255 / 255.0 blue: arc4random() % 255 / 255.0 alpha: 1.0];
    
    return cell;
}
更加灵活的流式布局
objectivec 复制代码
//实现delegate,自定义任何位置上cell的样式
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.item % 2) {
        return CGSizeMake((self.view.bounds.size.width - 12) / 3, (self.view.bounds.size.width - 12) / 3);
    } else {
        return CGSizeMake((self.view.bounds.size.width - 12) / 6, (self.view.bounds.size.width - 12) / 6);
    }
}

参差瀑布流布局

在很多应用程序中都有瀑布流效果,即分成两列或者多列进行数据的展示 ,每条数据itemcell的高度也随数据多少不同而显示得参差不齐

使用系统提供的原生UICollectionViewFlowLayout类进行布局设置很难实现这样的效果,开发者可以自定义一个它的子类来实现瀑布流式的效果

流布局又称瀑布流布局,是一种比较流行的网页布局模式,视觉效果多表现为参差不齐的多栏布局。

声明MyLayout类

创建一个布局类MyLayout,使其继承于UICollectionViewFlowLayout ,并新增一个属性itemCount用于设置要布局的item的数量:

MyLayout.h

objectivec 复制代码
@interface MyLayout : UICollectionViewFlowLayout
@property (nonatomic, assign)NSInteger itemCount;
@end

设置MyLayout相关属性

UICollectionViewLayout类提供了prepareLayout来做布局前的准备,这个时机会调用此方法,可以在其中设置布局配置数组:

MyLayout.m

objectivec 复制代码
@implementation MyLayout {
    //自定义的布局配置数组,保存每个cell的布局信息attribute
    NSMutableArray* _attributeArray;
}

//布局前的准备会调用这个方法
- (void)prepareLayout {
    _attributeArray = [[NSMutableArray alloc] init];
    [super prepareLayout];
    
    //为方便演示,设置为静态的2列
    //计算每一个Item的宽度
    //sectionInset表示item距离section四个方向的内边距 UIEdgeInsetsMake(top, left, bottom, right)
    CGFloat WIDTH = ([UIScreen mainScreen].bounds.size.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing) / 2;
    
    //创建数组保存每一列的高度(实际是总高度),这样就可以在布局时始终将下一个Item放在最短的列下面
    CGFloat colHeight[2] = {self.sectionInset.top, self.sectionInset.bottom};
    
    //遍历每一个Item来设置布局
    for (int i = 0; i < self.itemCount; ++i) {
        
        //每个Item在CollectionView中的位置
        NSIndexPath* indexPath = [NSIndexPath indexPathForItem: i inSection: 0];
        
        //通过indexPath创建一个布局属性类
        UICollectionViewLayoutAttributes* attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath: indexPath];
        
        
        //随机一个高度,在77~200之间
        CGFloat height = arc4random() % 123 + 77;
        
        //那一列高度小,则放到哪一列下面
        int indexCol = 0;  //标记短的列
        if (colHeight[0] < colHeight[1]) {
            //将新的Item高度加入到短的一列
            colHeight[0] = colHeight[0] + height + self.minimumLineSpacing;
            indexCol = 0;
        } else {
            colHeight[1] = colHeight[1] + height + self.minimumLineSpacing;
            indexCol = 1;
        }
        
        //设置Item的位置
        attris.frame = CGRectMake(self.sectionInset.left + (self.minimumInteritemSpacing + WIDTH) * indexCol, colHeight[indexCol] - height - self.minimumLineSpacing, WIDTH, height);
        
        [_attributeArray addObject: attris];

    }
    
    //给itemSize赋值,确保滑动范围在正确区间,这里是通过将所有的Item高度平均化计算出来的
    //(以最高的列为标准)
    if (colHeight[0] > colHeight[1]) {
        self.itemSize = CGSizeMake(WIDTH, (colHeight[0] - self.sectionInset.top) * 2 / self.itemCount - self.minimumLineSpacing);
    } else {
        self.itemSize = CGSizeMake(WIDTH, (colHeight[1] - self.sectionInset.top) * 2 / self.itemCount - self.minimumLineSpacing);
    }
}

//此系统提供的方法会返回设置好的布局数组
- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect: (CGRect)rect {
    return _attributeArray;
}

@end
  1. 先声明一个_attributeArray数组存放每个item的布局信息
  2. 在UICollectionView进行布局时,首先会调用其Layout布局类的prepareLayout方法,在这个方法中可以进行每个item布局属性的相关计算操作
  3. 具体每个item的布局属性实际是保存在UICollectionViewLayoutAttributes 类对象中的,其中包括sizeframe等信息,并与每个item一一对应
  4. prepareLayout方法准备好所有item的Attributes布局属性后,以数组的形式调用layoutAttributesForElementsInRect:方法来返回给UICollectionView进行界面的布局

ViewController.m引入MyLayout文件并使用这个类:

objectivec 复制代码
#import "MyLayout.h"

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor blueColor];
    
    //使用自定义的layout类
    MyLayout* myLayout = [[MyLayout alloc] init];
    myLayout.itemCount = 21;
    
    UICollectionView* collectionView = [[UICollectionView alloc] initWithFrame: self.view.bounds collectionViewLayout: myLayout];
    collectionView.dataSource = self;
    collectionView.delegate = self;
    
    [self.view addSubview: collectionView];
    
    [collectionView registerClass: [UICollectionViewCell class] forCellWithReuseIdentifier: @"MyUICollectionView"];
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 21;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell* myCell = [collectionView dequeueReusableCellWithReuseIdentifier: @"MyUICollectionView" forIndexPath: indexPath];
    
    myCell.backgroundColor = [UIColor colorWithRed: arc4random() % 255 / 255.0 green: arc4random() % 255 / 255.0 blue: arc4random() % 255 / 255.0 alpha: 1.0];
    
    return myCell;
}

运行结果:

圆环布局

通过上面的代码我们可知,UICollectionView的布局原理是采用Layout类进行每个item布局信息的配置的,具体的配置信息由UICollectionViewLayoutAttributes存储

明白了这个机制后,我们可以发挥想象实现更加炫酷复杂的布局效果

与参差瀑布式布局一样,这里附上圆环布局代码:

CircleLayout.h

objectivec 复制代码
@interface CircleLayout : UICollectionViewFlowLayout
@property (nonatomic, assign)NSInteger itemCount;
@end

CircleLayout.m

objectivec 复制代码
@implementation CircleLayout {
    NSMutableArray* _attributeArray;
}

- (void)prepareLayout {
    [super prepareLayout];
    
    //获取item的个数
    self.itemCount = (int)[self.collectionView numberOfItemsInSection: 0];
    _attributeArray = [[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大小为50*50,即半径为25
    for (int i = 0; i < self.itemCount; ++i) {
        NSIndexPath* indexPath = [NSIndexPath indexPathForItem: i inSection: 0];
        UICollectionViewLayoutAttributes* attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath: indexPath];
        
        //设置item大小
        attris.size = CGSizeMake(50, 50);
        
        //计算每个item中心坐标(圆心位置)
        float x = center.x + cosf(2 * M_PI / self.itemCount * i) * (radius - 25);
        float y = center.y + sinf(2 * M_PI / self.itemCount * i) * (radius - 25);
        attris.center = CGPointMake(x, y);
        
        [_attributeArray addObject: attris];
    }
}

//设置内容区域的大小
//作用同赋值contentSize属性一样,返回一个CollectionView可以滑动的范围尺寸
- (CGSize)collectionViewContentSize {
    return self.collectionView.frame.size;
}

- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return _attributeArray;
}
@end

ViewController.m

objectivec 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor blueColor];
    
    //使用自定义的layout类
//    MyLayout* myLayout = [[MyLayout alloc] init];
//    myLayout.itemCount = 21;
    CircleLayout* circleLayout = [[CircleLayout alloc] init];
    
    UICollectionView* collectionView = [[UICollectionView alloc] initWithFrame: self.view.bounds collectionViewLayout: circleLayout];
    collectionView.backgroundColor = [UIColor blackColor];
    collectionView.dataSource = self;
    collectionView.delegate = self;
    
    [self.view addSubview: collectionView];
    
//    [collectionView registerClass: [UICollectionViewCell class] forCellWithReuseIdentifier: @"MyUICollectionView"];
    [collectionView registerClass: [UICollectionViewCell class] forCellWithReuseIdentifier: @"CircleUICollectionView"];
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 11;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
//    UICollectionViewCell* myCell = [collectionView dequeueReusableCellWithReuseIdentifier: @"MyUICollectionView" forIndexPath: indexPath];
    UICollectionViewCell* circleCell = [collectionView dequeueReusableCellWithReuseIdentifier: @"CircleUICollectionView" forIndexPath: indexPath];
    
    circleCell.layer.masksToBounds = YES;
    circleCell.layer.cornerRadius = 25;
    circleCell.backgroundColor = [UIColor colorWithRed: arc4random() % 255 / 255.0 green: arc4random() % 255 / 255.0 blue: arc4random() % 255 / 255.0 alpha: 1.0];
    
    return circleCell;
}

@end

运行结果:

总结

UICollectionView其实算是特殊Flow布局的UITableView,但简单的列表仍可以使用UITableView

UICollectionView最大的优势就是通过自定义Layout,实现cell的布局,整体的思路就是:通过一些几何计算,设置好每个item的布局位置和大小

在之后的学习中,编者也将参考这片文章:一篇较为详细的 UICollectionView 使用方法总结

相关推荐
若水无华2 天前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"2 天前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy2 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克2 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨2 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆2 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂3 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T3 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20253 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz3 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频