本文主要介绍了如何自定义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上的。
.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
自定义的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 ()@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; }