使用
比如,
- 支持横向 + 纵向两个方向的布局
- 更加灵活的布局方式、动画
- 可以动态对布局进行重设(切换layout)
目录
-
- UICollectionView的基础使用
-
- 显示UICollectionView
- UICollectionViewLayout布局策略(UICollectionViewLayoutAttributes)
-
- UICollectionViewFlowLayout流式布局
- 九宫格布局
- 更加灵活的流式布局
- 参差瀑布流布局
-
- 声明MyLayout类
- 设置MyLayout相关属性
- 圆环布局
- 总结
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:
@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)
UICollectionViewFlowLayout流式布局
作为一个生成布局信息(item的大小、位置、3D变换等)的抽象类,要实际使用需要继承,比如系统提供的继承于
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属性
该类的
若将枚举值改为
前者当一行满时,另起一行;后者当一列满时,另起一列
minimumInteritemSpacing最小列间距属性
系统通过
flowLayOut.itemSize = CGSizeMake(self.view.frame.size.width / 2 - 12, 300);
九宫格布局
和
@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; }
更加灵活的流式布局
//实现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); } }
参差瀑布流布局
在很多应用程序中都有瀑布流效果,即分成两列或者多列进行数据的展示,每条数据
使用系统提供的原生
流布局又称瀑布流布局,是一种比较流行的网页布局模式,视觉效果多表现为参差不齐的多栏布局。
声明MyLayout类
创建一个布局类
MyLayout.h
@interface MyLayout : UICollectionViewFlowLayout @property (nonatomic, assign)NSInteger itemCount; @end
设置MyLayout相关属性
MyLayout.m
@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
- 先声明一个
_attributeArray 数组存放每个item的布局信息 - 在UICollectionView进行布局时,首先会调用其Layout布局类的
prepareLayout 方法,在这个方法中可以进行每个item 布局属性的相关计算操作 - 具体每个item的布局属性实际是保存在UICollectionViewLayoutAttributes类对象中的,其中包括
size 、frame 等信息,并与每个item一一对应 - 在
prepareLayout 方法准备好所有item的Attributes布局属性后,以数组的形式调用layoutAttributesForElementsInRect: 方法来返回给UICollectionView进行界面的布局
ViewController.m引入MyLayout文件并使用这个类:
#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的布局原理是采用
明白了这个机制后,我们可以发挥想象实现更加炫酷复杂的布局效果
与参差瀑布式布局一样,这里附上圆环布局代码:
CircleLayout.h
@interface CircleLayout : UICollectionViewFlowLayout @property (nonatomic, assign)NSInteger itemCount; @end
CircleLayout.m
@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
- (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最大的优势就是通过自定义
在之后的学习中,编者也将参考这片文章:一篇较为详细的 UICollectionView 使用方法总结