本文主要介绍了如何自定义UICollectionViewLayout。
一、简介UICollectionView的布局
首先我们回顾一下跟UICollectionView的几个类,看图说话。
简单解释上图,UICollectionView有个属性是collectionViewLayout,这个属性就是用来描述collectionView中的控件布局的,一般在初始化UICollectionView的时候,我们会传入一个UICollectionViewLayout对象来描述布局信息。常见的UICollectionViewFlowLayout (流水布局)太简单,本文就不赘述了,只需要知道它是官方提供的一个布局特例(好多布局信息都为我们写好了),继承自UICollectionViewLayout。其实,真正描述UICollectionView中cell等位置的是进一步封装在UICollectionViewLayout中的UICollectionViewLayoutAttributes(布局属性),attributes对象中包含了索要描述的控件的位置,大小,透明度,层级等信息。UICollectionViewLayout会把描述各个控件的attributes对象,封装到一个数组里面去,统一返回给collectionView,告诉collectionView就按照这个“说明书”来布局。
知道了这个关系,重点就是 自定义一个 UICollectionViewLayout 类,然后赋值给collectionView.collectionViewLayout。
补充下,collectionView的组成元素:
1、cell
2、header 、footer(统称SupplementaryView)
3、decorationView ,装饰,空间层次关系:在collectionView的背景之上,cell之下的一个视图,ibooks里面的那个书架就是它
二、自定义UICollectionViewLayout
首先建一个类继承自 UICollectionViewLayout,对了,每个Layout都有一个属性是collectionView,对应自己所属的collectionView。
@interface LTCollectionViewLayout : UICollectionViewLayout
其次,在.m文件中,实现以下方法(按系统调用它们的顺序写的,当然四五六方法是我们主动调用的)
(一)-(void)prepareLayout
在这个方法中一般进行一些数据的初始化,为下面的方法准备一些数据。记得一定要调用[super prepareLayout];
(二)-(CGSize)collectionViewContentSize
返回collectionView的可以滚动的区域大小。非必须重写方法,但笔者建议重写。
(三)-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
这个方法是最主要的,用来把所有的布局信息打包到一个数组里面,返回,告诉系统怎么布局。在该方法中我们主动调用下面三个方法获取想要的布局信息。
(四)-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
返回indexPath处cell的布局信息,也就是说,cell的布局代码在这个方法里面写。
(五)如果有各组有header或footer,or both,就实现
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
返回indexPath处的header或footer的布局信息
(六)如果某一section有装饰,就实现
-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
返回indexPath处的装饰的布局信息
其实,到这里,我们基本上已经知道怎么自定义layout了,下面给出一个非常简单的demo。
先上图,git上的demo。
.h文件
@interface LTCollectionViewLayout : UICollectionViewLayout
@end
.m文件
#define decorationView @"decorationView"
#import "LTCollectionViewLayout.h"
#import "LTDecotationView.h"
@interface LTCollectionViewLayout()
@property(nonatomic,readonly)CGFloat cellCount;
@property(nonatomic,readonly)CGPoint center;
@property(nonatomic,readonly)CGFloat radius;
@end
@implementation LTCollectionViewLayout
-(void)prepareLayout
{
[super prepareLayout];
NSLog(@"prepareLayout");
CGSize size=self.collectionView.frame.size;
_cellCount=[[self collectionView] numberOfItemsInSection:0];
// 圆心
_center=CGPointMake(size.height/3.5, size.width/1.5);
// 半径
_radius=MIN(size.height, size.width)/2.5;
// 如果用到了装修,那么必须注册装饰,官方提供了以下两个方法
// - (void)registerClass:(Class)viewClass forDecorationViewOfKind:(NSString *)elementKind;
// - (void)registerNib:(UINib *)nib forDecorationViewOfKind:(NSString *)elementKind;
// 注册装饰
[self registerClass:[LTDecotationView class] forDecorationViewOfKind:decorationView];
}
/**
* 此方法返回collectionView可以滚动的区域大小
*
*/
-(CGSize)collectionViewContentSize
{
NSLog(@"collectionViewContentSize");
return CGSizeMake(375, 2000);
}
/**
* 描述每个cell的布局信息,包括大小,位置等,布局信息封装到UICollectionViewLayoutAttributes对象中
*/
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"layoutAttributesForItemAtIndexPath");
UICollectionViewLayoutAttributes *attributes=[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// 每个cell的大小
attributes.size=CGSizeMake(34.0 , 34.0);
// 使用三角函数计算每个cell的中点位置
attributes.center=CGPointMake(_center.x + _radius*cosf(2*M_PI*indexPath.item/_cellCount), _center.y+_radius*sinf(2*M_PI*indexPath.item/_cellCount));
return attributes;
}
/**
* 该方法返回值也是UICollectionViewLayoutAttributes对象,attributes对象中已经包含了SupplementaryView的布局信息,即header或footer,区分的方法是ofKind:后面的字符串
*/
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
// 注意:初始化方法和cell、DecorationView不同
UICollectionViewLayoutAttributes *attributes=[UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:indexPath];
return attributes;
}
/**
使用了装饰,实现这个方法来描述DecorationView的布局
* 该方法返回值也是UICollectionViewLayoutAttributes对象,attributes对象中已经包含了装饰,使用它需要在自定义的layout的prepareLayout方法中注册,因为collectionView没有提供它的注册方法
*
*/
-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
// 注意:初始化方法和cell、SupplementaryView不同
UICollectionViewLayoutAttributes *attributes=[UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:@"decorationView" withIndexPath:indexPath];
// 放在圆心处
attributes.center=self.center;
// 尺寸
attributes.size=CGSizeMake(150,30);
return attributes;
}
/**
* 对一次显示某个区域的时候自动调用,对显示过的区域不会再调用该方法
*/
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSLog(@"layoutAttributesForElementsInRect,%@",NSStringFromCGRect(rect));
NSMutableArray *attributes=[NSMutableArray array];
// 1、添加cell的布局描述
for (int i =0; i<self.cellCount; i++) {
NSIndexPath *indexPath=[NSIndexPath indexPathForItem:i inSection:0];
// 调用对应的方法获得indexPath处的cell布局信息,并添加到数组中
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
// 2、,如果有,添加header或footer的布局描述
NSIndexPath *indexPath=[NSIndexPath indexPathForItem:0 inSection:0];
// 调用对应的方法获得indexPath处的header或footer布局信息,并添加到数组中
// [attributes addObject:[self layoutAttributesForSupplementaryViewOfKind:@"header or footer" atIndexPath:indexPath]];
// 3、如果有,添加decorationView的布局描述
// 调用对应的方法获取indexPath处decorationView的布局信息
[attributes addObject:[self layoutAttributesForDecorationViewOfKind:@"decorationView" atIndexPath:indexPath]];
// 4、返回装有各种布局信息的数组给collectionView
return attributes;
}
@end
自定义的decoration,
.h
@interface LTDecotationView : UICollectionReusableView
@end
.m
#import "LTDecotationView.h"
@implementation LTDecotationView
-(instancetype)initWithFrame:(CGRect)frame
{
if (self=[super initWithFrame:frame]) {
UILabel *label=[[UILabel alloc]initWithFrame:self.bounds];
label.text=@"DecotationView";
label.backgroundColor=[UIColor whiteColor];
[self addSubview:label];
}
return self;
}
@end
控制器.m文件
#define collectionCell @"collectionCell"
#import "ViewController.h"
#import "LTCollectionViewLayout.h"
@interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>
@property(nonatomic,strong)UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1、初始化collectionView
LTCollectionViewLayout *myLayout=[[LTCollectionViewLayout alloc]init];
UICollectionView * myCollectionView=[[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, 375, 667) collectionViewLayout:myLayout];
myCollectionView.backgroundColor=[UIColor purpleColor];
self.collectionView=myCollectionView;
[self.view addSubview:myCollectionView];
// 2、设置代理
self.collectionView.delegate=self;
self.collectionView.dataSource=self;
// 3、注册cell
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:collectionCell];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 20;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:collectionCell forIndexPath:indexPath];
cell.backgroundColor=[UIColor orangeColor];
return cell;
}