⼀、什么是集合视图
在iOS6.0之后,苹果推出了⼀个新的继承于UIScrollView的⼀个视 图,UICollectionView,也被称之为集合视图。和UITableView共同作为 在开发中⾮常常⽤的两个视图,常常作为项⽬的主界⾯出现。
⼆、创建UICollectionView
UICollection的实现跟tableView不⼀样的地⽅在于Item的布局 稍微复杂⼀点,需要⽤UICollectionViewLayout类来描述视图的 布局。我们在项⽬中常⽤的是系统提供的 UICollectionViewFlowLayout类,也可以⾃定义 UICollectionViewLayout。
//创建一个布局对象,采用系统布局类UICollectionViewFlowLayout
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(120, 200);
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 7.5;
layout.sectionInset = UIEdgeInsetsMake(50, 0, 0, 0);
//设置最小的行间距
layout.minimumLineSpacing = 20;
//设置item与item之间的间距
layout.minimumInteritemSpacing = 10;
//设置集合视图的分区间隔
layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
//设置集合视图的滑动方向
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
CGFloat totalWidth = self.view.frame.size.width;
//设置每一个item的尺寸大小
layout.itemSize = CGSizeMake((totalWidth - 40) / 3, 80);
layout.headerReferenceSize = CGSizeMake(totalWidth, 40);
//集合视图的创建,必须指定布局,如果没有布局,显示不了任何东西。
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];
collectionView.dataSource = self;//数据源
collectionView.delegate = self;//代理
// collectionView.backgroundColor = [UIColor redColor];
//集合视图如果想要显示内容,必须将cell进行注册
[collectionView registerClass:[YourCollectionViewCell class] forCellWithReuseIdentifier:kStr];
//集合视图如果想要分区头视图显示,必须注册增广视图
[collectionView registerClass:[YourCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(120, 200);
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 7.5;
layout.sectionInset = UIEdgeInsetsMake(50, 0, 0, 0);
//设置最小的行间距
layout.minimumLineSpacing = 20;
//设置item与item之间的间距
layout.minimumInteritemSpacing = 10;
//设置集合视图的分区间隔
layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
//设置集合视图的滑动方向
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
CGFloat totalWidth = self.view.frame.size.width;
//设置每一个item的尺寸大小
layout.itemSize = CGSizeMake((totalWidth - 40) / 3, 80);
layout.headerReferenceSize = CGSizeMake(totalWidth, 40);
//集合视图的创建,必须指定布局,如果没有布局,显示不了任何东西。
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];
collectionView.dataSource = self;//数据源
collectionView.delegate = self;//代理
// collectionView.backgroundColor = [UIColor redColor];
//集合视图如果想要显示内容,必须将cell进行注册
[collectionView registerClass:[YourCollectionViewCell class] forCellWithReuseIdentifier:kStr];
//集合视图如果想要分区头视图显示,必须注册增广视图
[collectionView registerClass:[YourCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
[self.view addSubview:collectionView];
UICollectionView和UITableView⼀样,也需要遵守两个代理协 议:UICollectionViewDelegate和UICollectionViewDataSource。
当遵守了这两个代理协议之后,UICollectionView才可以正常 的显⽰。
@interface ViewController : UIViewController<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@end
UICollectionView和UITableView⼀样有两个必须实现的代理⽅法。
返回多少个Item;
指定每个item的样式。
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 6;
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
YourCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kStr forIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor colorWithRed:arc4random() % 256 / 255.0 green:arc4random() % 256 / 255.0 blue:arc4random() % 256 / 255.0 alpha:1.0];
cell.numberLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row];
return cell;
}
return 6;
}
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
YourCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kStr forIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor colorWithRed:arc4random() % 256 / 255.0 green:arc4random() % 256 / 255.0 blue:arc4random() % 256 / 255.0 alpha:1.0];
cell.numberLabel.text = [NSString stringWithFormat:@"%ld",indexPath.row];
return cell;
}
选择实现的代理方法
//设置分区个数 返回有多少个分区
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 10;
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 10;
}
//item点击之后触发的方法
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%ld %ld",indexPath.section, indexPath.row);
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%ld %ld",indexPath.section, indexPath.row);
}
UICollectionView不能像UITableView⼀样直接指定头部和尾部视 图,需要注册使⽤,最⼤的好处是添加了重⽤机制。
//返回增广视图,也就是集合视图的头视图或者尾视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
YourCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header" forIndexPath:indexPath];
view.headerLabel.text = [NSString stringWithFormat:@"当前分区为:%ld",indexPath.section];
view.backgroundColor = [UIColor yellowColor];
return view;
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
YourCollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header" forIndexPath:indexPath];
view.headerLabel.text = [NSString stringWithFormat:@"当前分区为:%ld",indexPath.section];
view.backgroundColor = [UIColor yellowColor];
return view;
}
三、布局协议
布局协议:UICollectionViewDelegateFlowLayout,它是对 UICollectionViewDelegate的扩展
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake((kWidth - 40) / 3, 100);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 0, 0, 0);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 20;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 20;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(kWidth, 40);
return CGSizeMake((kWidth - 40) / 3, 100);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 0, 0, 0);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 20;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 20;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(kWidth, 40);
}
四、⾃定义UICollectionViewLayout
系统给我们提供的 UICollectionViewFlowLayout布局类不 能实现瀑布流的效果,如果我们想实现 瀑布流的效果,需要⾃定义⼀个 UICollectionViewLayout类,实现瀑布 流效果。
我们需要⼀个图⽚⼤⼩和图⽚地址的Json数据,和SDWebImage 图⽚加载的第三⽅⼯具。
第一步:创建⼀个继承于UICollectionViewLayout的类,声明⼀个代理协议,并声明协议⽅法。
@protocol WaterFlowLayoutDelegate <NSObject>
//关键方法,此方法的作用是返回每一个item的size大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
//关键方法,此方法的作用是返回每一个item的size大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterFlowLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
@end
@interface WaterFlowLayout : UICollectionViewLayout
@property (nonatomic, assign) id<WaterFlowLayoutDelegate>delegate;
第二步:
在接⼝⽂件中声明需要提供给外界使⽤的属性。
//瀑布流一共多少列
@property (nonatomic, assign) NSUInteger numberOfColumn;
// item的⼤⼩
@property (nonatomic, assign) CGSize itemSize;
// 内边距
@property (nonatomic, assign) UIEdgeInsets sectionInsets;
// item的间距
@property (nonatomic, assign) CGFloat insertItemSpacing;
// 代理⼈
@property (nonatomic, assign) NSUInteger numberOfColumn;
// item的⼤⼩
@property (nonatomic, assign) CGSize itemSize;
// 内边距
@property (nonatomic, assign) UIEdgeInsets sectionInsets;
// item的间距
@property (nonatomic, assign) CGFloat insertItemSpacing;
// 代理⼈
@property (nonatomic, assign) id<WaterFlowLayoutDelegate>delegate;
@end
第三步:
在实现⽂件中声明⼀些不需要提供给外界的⽅法和属性。
// WaterFlowLayout.m
@interface WaterFlowLayout ()
// 获取item的总数量
@property (nonatomic, assign) NSUInteger numberOfItems;
//存放每一列的高度
@property (nonatomic, retain) NSMutableArray *columnHeightsArray;
//存放 每一个item的 属性 包含 frame以及下标(x,y,w,h)
@property (nonatomic, retain) NSMutableArray *attributesArray;
//保存每个Item的X值
@property (nonatomic, assign) CGFloat detalX;
//保存每个Item的Y值
@property (nonatomic, assign) CGFloat detalY;
//记录最短列
@property (nonatomic, assign) NSInteger shortestIndex;
//获取最长列的索引
- (NSInteger)p_indexForLongestColumn;
//获取最短列的索引
- (NSInteger)p_indexForShortestColumn;
// 获取item的总数量
@property (nonatomic, assign) NSUInteger numberOfItems;
//存放每一列的高度
@property (nonatomic, retain) NSMutableArray *columnHeightsArray;
//存放 每一个item的 属性 包含 frame以及下标(x,y,w,h)
@property (nonatomic, retain) NSMutableArray *attributesArray;
//保存每个Item的X值
@property (nonatomic, assign) CGFloat detalX;
//保存每个Item的Y值
@property (nonatomic, assign) CGFloat detalY;
//记录最短列
@property (nonatomic, assign) NSInteger shortestIndex;
//获取最长列的索引
- (NSInteger)p_indexForLongestColumn;
//获取最短列的索引
- (NSInteger)p_indexForShortestColumn;
@end
第四步:
数据源的懒加载⽅法
//懒加载
- (NSMutableArray *)columnHeightsArray {
if (!_columnHeightsArray) {
self.columnHeightsArray = [NSMutableArray array];
}
return _columnHeightsArray;
}
- (NSMutableArray *)attributesArray {
if (!_attributesArray) {
self.attributesArray = [NSMutableArray array];
}
return _attributesArray;
- (NSMutableArray *)columnHeightsArray {
if (!_columnHeightsArray) {
self.columnHeightsArray = [NSMutableArray array];
}
return _columnHeightsArray;
}
- (NSMutableArray *)attributesArray {
if (!_attributesArray) {
self.attributesArray = [NSMutableArray array];
}
return _attributesArray;
}
第五步:
//获取最小高度的方法
- (CGFloat)minHeight
{
CGFloat min = 100000;
for (NSNumber *height in _columnHeightsArray) {
CGFloat h = [height floatValue];
if (min > h) {
min = h;
}
}
return min;
}
//获取最大值
- (CGFloat)maxHeight
{
CGFloat max = 0;
for (NSNumber *height in _columnHeightsArray) {
CGFloat h = [height floatValue];
if (max < h) {
max = h;
}
}
return max;
- (CGFloat)minHeight
{
CGFloat min = 100000;
for (NSNumber *height in _columnHeightsArray) {
CGFloat h = [height floatValue];
if (min > h) {
min = h;
}
}
return min;
}
//获取最大值
- (CGFloat)maxHeight
{
CGFloat max = 0;
for (NSNumber *height in _columnHeightsArray) {
CGFloat h = [height floatValue];
if (max < h) {
max = h;
}
}
return max;
}
第六步:
获取索引
//获取最短列索引
- (NSUInteger) p_indexForShortestColumn
{
//记录索引
NSUInteger index = 0;
for (int i = 0; i < [_columnHeightsArray count]; i ++) {
//获取当前高度
CGFloat height = [_columnHeightsArray[i] floatValue];
if (height == [self minHeight]) {
index = i;
return index;
}
}
return index;
//记录索引
NSUInteger index = 0;
for (int i = 0; i < [_columnHeightsArray count]; i ++) {
//获取当前高度
CGFloat height = [_columnHeightsArray[i] floatValue];
if (height == [self minHeight]) {
index = i;
return index;
}
}
return index;
}
//获取最长列的索引
- (NSInteger)p_indexForLongestColumn {
//记录哪一列最长
NSInteger longestIndex = 0;
for (int i = 0; self.numberOfColumn; i ++) {
//获取高度
CGFloat currentHeight = [self.columnHeightsArray[i] floatValue];
if (currentHeight == [self maxHeight]) {
longestIndex = i;
}
}
return longestIndex;
- (NSInteger)p_indexForLongestColumn {
//记录哪一列最长
NSInteger longestIndex = 0;
for (int i = 0; self.numberOfColumn; i ++) {
//获取高度
CGFloat currentHeight = [self.columnHeightsArray[i] floatValue];
if (currentHeight == [self maxHeight]) {
longestIndex = i;
}
}
return longestIndex;
}
第七步:
//重写父类的布局方法
- (void)prepareLayout
{
[super prepareLayout];
_attributesArray = [[NSMutableArray alloc] init];
_columnHeightsArray = [[NSMutableArray alloc] initWithCapacity:self.numberOfColumn];
//给列高数组里面的对象赋初值
for (int i = 0; i < self.numberOfColumn; i ++) {
[_columnHeightsArray addObject:@0.0];
}
CGFloat totalWidth = self.collectionView.frame.size.width;
//创建 每个item frame中的x、y
CGFloat x = 0;
CGFloat y = 0;
NSUInteger itemCount = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < itemCount; i ++) {
//得到集合视图中 列间隙的个数
NSUInteger numberOfSpace = self.numberOfColumn - 1;
//代理对象执行代理方法,得到 item之间的间隙大小
CGFloat spaceWidth = [_delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:0];
//求每列的宽度,也就是每个item的width
CGFloat width = (totalWidth - spaceWidth * numberOfSpace) / self.numberOfColumn;
//获取每一个itemSize的大小
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//数据中原始图片大小
CGSize imageSize = [_delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
//通过 约分公式得到固定宽之后的高度是多少
CGFloat height = width * imageSize.height / imageSize.width;
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//记录每一个item的大小和位置
attribute.frame = CGRectMake(x, y, width, height);
//数组保存每个item的位置信息
[_attributesArray addObject:attribute];
NSLog(@"item = %d",i);
NSLog(@"x = %.2f y = %.2f width = %.2f height = %.2f",x,y,width,height);
//求列高最小的那一列的下标
NSUInteger minHeightIndex = [self indexOfMinHeight];
//求出最小列的高度
CGFloat minHeight = [_columnHeightsArray[minHeightIndex] floatValue];
//求出行高
CGFloat lineHeight = [_delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:0];
//上一次总的列高 加上 行高 加上新加上的item的height,才是现在这一列的总高度
//minHeight为最小列现在的高度
//lineHeight为行间距
//height为新加的item的高
_columnHeightsArray[minHeightIndex] = [NSNumber numberWithFloat:minHeight + lineHeight + height];
//重新算最小列高的下标
minHeightIndex = [self indexOfMinHeight];
//算下一次新加的item的x和y值
x = (spaceWidth + width) * minHeightIndex;
y = [self minHeight];
}
}
//重写这个方法,可以返回集合视图的总高度
- (CGSize)collectionViewContentSize
{
return CGSizeMake(self.collectionView.frame.size.width, [self maxHeight]);
}
//这个方法不写 集合视图显示不出来,这个方法是将保存的每个item的信息告诉集合视图,进行显示。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return _attributesArray;
{
[super prepareLayout];
_attributesArray = [[NSMutableArray alloc] init];
_columnHeightsArray = [[NSMutableArray alloc] initWithCapacity:self.numberOfColumn];
//给列高数组里面的对象赋初值
for (int i = 0; i < self.numberOfColumn; i ++) {
[_columnHeightsArray addObject:@0.0];
}
CGFloat totalWidth = self.collectionView.frame.size.width;
//创建 每个item frame中的x、y
CGFloat x = 0;
CGFloat y = 0;
NSUInteger itemCount = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < itemCount; i ++) {
//得到集合视图中 列间隙的个数
NSUInteger numberOfSpace = self.numberOfColumn - 1;
//代理对象执行代理方法,得到 item之间的间隙大小
CGFloat spaceWidth = [_delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:0];
//求每列的宽度,也就是每个item的width
CGFloat width = (totalWidth - spaceWidth * numberOfSpace) / self.numberOfColumn;
//获取每一个itemSize的大小
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
//数据中原始图片大小
CGSize imageSize = [_delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
//通过 约分公式得到固定宽之后的高度是多少
CGFloat height = width * imageSize.height / imageSize.width;
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//记录每一个item的大小和位置
attribute.frame = CGRectMake(x, y, width, height);
//数组保存每个item的位置信息
[_attributesArray addObject:attribute];
NSLog(@"item = %d",i);
NSLog(@"x = %.2f y = %.2f width = %.2f height = %.2f",x,y,width,height);
//求列高最小的那一列的下标
NSUInteger minHeightIndex = [self indexOfMinHeight];
//求出最小列的高度
CGFloat minHeight = [_columnHeightsArray[minHeightIndex] floatValue];
//求出行高
CGFloat lineHeight = [_delegate collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:0];
//上一次总的列高 加上 行高 加上新加上的item的height,才是现在这一列的总高度
//minHeight为最小列现在的高度
//lineHeight为行间距
//height为新加的item的高
_columnHeightsArray[minHeightIndex] = [NSNumber numberWithFloat:minHeight + lineHeight + height];
//重新算最小列高的下标
minHeightIndex = [self indexOfMinHeight];
//算下一次新加的item的x和y值
x = (spaceWidth + width) * minHeightIndex;
y = [self minHeight];
}
}
//重写这个方法,可以返回集合视图的总高度
- (CGSize)collectionViewContentSize
{
return CGSizeMake(self.collectionView.frame.size.width, [self maxHeight]);
}
//这个方法不写 集合视图显示不出来,这个方法是将保存的每个item的信息告诉集合视图,进行显示。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return _attributesArray;
}