UICollectionView是iOS6之后引入的一个新的UI控件,它和UITableView有着诸多相似之处,很多代理方法都十分类似,但UICollectionView是比UITableView更加强大的控件,有如下几个方面
#import "ViewController.h"
@interface ViewController () <UICollectionViewDelegate, UICollectionViewDataSource>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
//layout布局类
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
//布局方向为垂直流布局
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
//layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
//设置每个item的大小
layout.itemSize = CGSizeMake(120, 100);
_collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
//代理设置
_collectionView.delegate = self;
_collectionView.dataSource = self;
//注册item类型,这里使用系统类型
//必须注册!!!!,注册类似于tableView,但又不同于tableView,因为tableView有不注册的方法,临时创建
//iOS6之后的新类,统一从cell复用池获取cell,没有提供返回nil的方式
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
[self.view addSubview:_collectionView];
}
//返回分区个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
//返回每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 30;
}
//返回每个item
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
//只能用上面这个方法获取,其他方法都会崩溃
//UICollectionViewCell *cell = [[UICollectionViewCell alloc] init];
cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1];
return cell;
}
这个协议主要用于collectionView相关数据的处理
首先有两个方法是必须要实现的
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
下面的方法都是可选实现的
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;
这个协议用来设置和处理collectionView的功能和一些逻辑,所有的方法都是可选实现
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
- (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);
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (nonnull UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;
布局的管理类UICollectionViewFlowLayout
关于布局的相关设置和属性方法,通过对layout的设置,我们可以编写更加灵活的布局效果
前面的基础九宫格的布局方式中规中矩,有时候并不能满足我们的需求,有时我们需要对每一个item展示不同的大小
在上面那份代码中添加这个方法的实现
//设置每个item的大小,双数的为70*70 单数的为120*120
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row%2==0) {
return CGSizeMake(70, 70);
}else{
return CGSizeMake(120, 120);
}
}
UICollectionViewFlowLayout是系统提供给我们一个封装好的流布局设置类,其中一些布局属性我们可以进行设置
@property (nonatomic) CGFloat minimumLineSpacing;
@property (nonatomic) CGFloat minimumInteritemSpacing;
@property (nonatomic) CGSize itemSize;
@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);
@property (nonatomic) UICollectionViewScrollDirection scrollDirection;
@property (nonatomic) CGSize headerReferenceSize;
@property (nonatomic) CGSize footerReferenceSize;
@property (nonatomic) UIEdgeInsets sectionInset;
@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);
@property (nonatomic) BOOL sectionFootersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);
上面的方法在创建FlowLayout时静态的进行设置,如果我们需要动态的设置这些属性风,就像例子中的每个item大小都有差异,我们就可以通过代理方法来实现
UICollectionViewDelegateFlowLayout是UICollectionViewDelegate的子协议,常用方法如下,这些方法的实现都是可选的
@protocol UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>
@optional
//动态设置每个item的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
//动态设置每个分区的EdgeInsets
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
//动态设置每行的间距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
//动态设置每列的间距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
//动态设置某个分区头视图大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
//动态设置某个分区尾视图大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
@end
前面对UICollectionView的基础应用和设置UICollectionViewFlowLayout更加灵活的进行布局,但都限制在系统为我们准备好的布局框架中,还是有些局限性
例如要实现瀑布流似得不定高布局,前面的方法就很难满足我们的需求了。这种布局在app中的应用更加广泛,商品的展等都会倾向于采用这种的布局方式
通过自定义FlowLayout,我们也很容易实现
先新建一个文件继承于UICollectionViewFlowLayout
这里不做更多的封装,只添加一个属性,直接传入一个item个数,我们的重心是在重写布局的方法上
@interface MyLayout : UICollectionViewFlowLayout
@property (nonatomic, assign) int itemCount;
@end
UICollectionViewFlowLayout是一个专门用来管理collectionView布局的类,因此collectionView在进行UI布局前,都会通过这个类获取相关的布局信息。
FlowLayout类将这些布局信息全部存放到了一个数组中,数组中是UICollectionViewLayoutAttributes类,这个类是对item布局的具体设置。
总之,FlowLayout类将每个item的位置等布局信息放在一个数组中,在collectionView布局时,会调用FlowLayout类的layoutAttributesForElementsInRect:方法来获取这个布局配置数组。
因此我们需要重写这个方法,返回我们自定义的配置数组。
另外,FlowLayout类在进行布局之前,会调用prepareLayout方法,所以我们可以重写这个方法,在这里面对我们自定义配置数据进行一些设置。
简单来说,自定义一个FlowLayout布局类就是两个步骤
示例代码:
#import "MyLayout.h"
@implementation MyLayout {
//自定义的配置数组
NSMutableArray *_attributeArray;
}
//数组的相关设置在这个方法中
//布局前的准备 会调用这个方法
- (void)prepareLayout {
_attributeArray = [[NSMutableArray alloc] init];
[super prepareLayout];
//设置为静态的2列
//计算每一个item的宽度
float itemWidth = ([UIScreen mainScreen].bounds.size.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing ) / 2;
//定义数组保存每一列的高度
//这个数组的主要作用是保存每一列的总高度,这个样在布局时,我们可以始终将下一个item放在最短的列下面
CGFloat colHeight[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];
//随意一个高度 在60-260之间
CGFloat height = arc4random() % 200 + 60;
//哪一行高度小 则放到哪一列下面
//标记最短的列
int flag = 0;
if (colHeight[0] < colHeight[1]) {
//将新的item高度加入到短的一列
colHeight[0] = colHeight[0] + height + self.minimumLineSpacing;
flag = 0;
} else {
colHeight[1] = colHeight[1] + height + self.minimumLineSpacing;
flag = 1;
}
//设置item的位置
attris.frame = CGRectMake(self.sectionInset.left + (self.minimumInteritemSpacing + itemWidth) * flag, colHeight[flag] - height - self.minimumLineSpacing, itemWidth, height);
[_attributeArray addObject:attris];
}
//设置itemSize来确保滑动范围的正确 这里是通过将所有的item高度平均化,计算出来的 (以最高的列为标准)
if (colHeight[0] > colHeight[1]) {
self.itemSize = CGSizeMake(itemWidth, (colHeight[0] - self.sectionInset.top) * 2 / _itemCount - self.minimumLineSpacing);
} else {
self.itemSize = CGSizeMake(itemWidth, (colHeight[0] - self.sectionInset.top) * 2 / _itemCount - self.minimumLineSpacing);
}
}
//返回布局数组
- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
return _attributeArray;
}
@end
//
#import "ViewController.h"
#import "MyLayout.h"
@interface ViewController () < UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
MyLayout *layout = [[MyLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.itemCount = 30;
UICollectionView *collection = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
collection.delegate = self;
collection.dataSource = self;
[collection registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
[self.view addSubview:collection];
}
//返回分区个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
//返回每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 30;
}
//返回每个item
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1];
return cell;
}
//配置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;
通过上面的属性,可以布局出各式各样的炫酷效果
前面的不定高瀑布流布局中,通过设置具体的布局属性类UICollectionViewLayoutAttributes来设置每一个item的具体位置
如果位置我们可以自由控制,那么布局也就更加灵活
先定义一个layout类,继承于UICollectionViewLayout,这个类是一个布局抽象基类,我们要使用自定义的布局方式,必须将其子类化。
在瀑布流布局时就使用过UICollectionViewFlowLayout类,这个类就是继承于UICollectionViewLayout类,是系统为我们实现好的一个布局方案
//设计一个圆环布局
@interface CircleLayout : UICollectionViewLayout
@property(nonatomic,assign)int itemCount;
@end
//
#import "CircleLayout.h"
@implementation CircleLayout {
NSMutableArray *_attributeArray;
}
- (void)prepareLayout {
[super prepareLayout];
//获取item个数
_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 < _itemCount; i++) {
UICollectionViewLayoutAttributes *attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
//设置item大小
attris.size = CGSizeMake(50, 50);
//计算每个item的圆心位置
float x = center.x + cosf(2 * M_PI / _itemCount * i) * (radius - 25);
float y = center.y + sinf(2 * M_PI / _itemCount * i) * (radius - 25);
attris.center = CGPointMake(x, y);
[_attributeArray addObject:attris];
}
}
//设置内容区域大小
- (CGSize)collectionViewContentSize {
return self.collectionView.frame.size;
}
//返回设置数组
- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
return _attributeArray;
}
@end
//----------------------------------------------------------------------
//ViewController.m
#import "ViewController.h"
#import "MyLayout.h"
#import "CircleLayout.h"
@interface ViewController () < UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
CircleLayout *layout = [[CircleLayout alloc] init];
UICollectionView *collection = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
collection.delegate = self;
collection.dataSource = self;
[collection registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
[self.view addSubview:collection];
}
//返回分区个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
//返回每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 20;
}
//返回每个item
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
//只能用上面这个方法获取,其他方法都会崩溃
//UICollectionViewCell *cell = [[UICollectionViewCell alloc] init];
cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1];
cell.layer.cornerRadius = cell.frame.size.width / 2;
cell.layer.masksToBounds = YES;
return cell;
}
参考