iOS学习之UICollectionView
一、什么是集合视图
在iOS6.0之后,苹果推出了一个新的继承于UIScrollView的一个视图,UICollectionView,也被称之为集合视图。
图例:
二、创建UICollectionView
1、UICollectionView跟tableView实现的不同:UICollectionView的Item的布局复杂一点,需要用UICollectionViewLayout类来描述视图的布局,常用的是系统提供的UICollectionViewFlowLayout类,也可以自定义UICollectionViewLayout。
2、创建UICollectionViewFlowLayout
// 1.定义collectionView的样式
self.myFlowLayout = [[UICollectionViewFlowLayout alloc] init];
// 设置属性
// 给定Item的大小(单元格)
self.myFlowLayout.itemSize = CGSizeMake((self.bounds.size.width - 40) / 3, (self.bounds.size.width - 40) / 3);
// 每两个Item的最小间隙(垂直滚动)
self.myFlowLayout.minimumInteritemSpacing = 10;
// 每两个Item的最小间隙(水平滚动方向)
self.myFlowLayout.minimumLineSpacing = 10;
// 设置滚动方向(Vertical垂直方向,horizontal水平方向)
self.myFlowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
// 设置视图的内边距(逆时针顺序)
self.myFlowLayout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
// 设置头视图,尾视图的大小(宽度不起作用,默认视图宽等屏幕宽)
self.myFlowLayout.headerReferenceSize = CGSizeMake(0, 50);
self.myFlowLayout.footerReferenceSize = CGSizeMake(0, 50);
3、初始化UICollectionView
// 2.布局collectionView
// 创建对象,并指定样式
self.collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:self.myFlowLayout];
4、设置代理,遵守协议
// 遵守协议
@interface RootViewController ()
@end
@implementation RootViewController
// 设置代理
self.rootView.collectionView.dataSource = self;
self.rootView.collectionView.delegate = self;
5、注册cell
因为UICollectionViewCell没有提供任何子控件,所以在项目中需要自定义Item。
// 注册cell
[self.rootView.collectionView registerClass:[RootCell class] forCellWithReuseIdentifier:identifier_cell];
6、实现代理方法
UICollectionView和UITableView一样有两个必须实现的代理方法:
返回多少个Item指定每个Item的样式。
// 设置每个分区里 有几个item
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
// 返回每一个item的cell对象
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
RootCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier_cell forIndexPath:indexPath];
cell.backgroundColor = [UIColor lightGrayColor];
return cell;
}
l 可选的代理方法:
l 设置多少个分区
l 设置分区头视图和尾视图:设置的视图需要自定义,和注册
l 点击item:可以通过点击item跳转视图
// 注册头视图,尾视图
[self.rootView.collectionView registerClass:[HeaderReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView"];
[self.rootView.collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footerView"];
// 设置多少个分区
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 3;
}
// 返回头视图和尾视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
// 判断头视图还是尾视图
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
HeaderReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"headerView" forIndexPath:indexPath];
headerView.backgroundColor = [UIColor redColor];
return headerView;
}else {
UICollectionReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"footerView" forIndexPath:indexPath];
footerView.backgroundColor = [UIColor greenColor];
return footerView;
}
}
// 点击Item
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
SecondViewController *secondVC = [[SecondViewController alloc] init];
[self.navigationController pushViewController:secondVC animated:YES];
}
7、完成效果
三、布局协议
布局协议:UICollectionViewDelegateFlowLayout,它是对UICollectionViewDelegate的扩展
@protocol UICollectionViewDelegateFlowLayout
@optional
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (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
四、自定义UICollectionViewLayout
系统提供的UICollectionViewFlowLayout布局类能实现的效果有限,如果我们有自己想要实现的效果,就需要自定义一个UICollectionViewLayout类。下面给大家举一个实现瀑布流效果的自定义布局:
在AppDelegate.m
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 创建UIWindow对象
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 设置背景颜色
self.window.backgroundColor = [UIColor whiteColor];
// 使window显示 [self.window makeKeyAndVisible];
// 创建视图控制器,给window指定根控制器
self.window.rootViewController = [[RootViewController alloc] init];
return YES;
}
在RootViewController.m
#import "RootViewController.h"#import "DataModel.h"#import "WaterFlowLayout.h"#import "RootCell.h"#import "UIImageView+WebCache.h"@interface RootViewController ()
@property (nonatomic, strong) NSMutableArray *allDataArr;// 定义collectionView
@property (nonatomic, strong) UICollectionView *collectionView;
@end
@implementation RootViewController// 定义全局静态变量static NSString * const identifier_cell = @"identifier_cell";
// 懒加载
-(NSMutableArray *)allDataArr
{
if (!_allDataArr) {
_allDataArr = [NSMutableArray array];
}
return _allDataArr;
}- (void)viewDidLoad {
[super viewDidLoad];
// 读取数据 [self loadData];
// 初始化布局 [self initLayout];
// 注册cell
[self.collectionView registerClass:[RootCell class] forCellWithReuseIdentifier:identifier_cell];
}
// 初始化布局
- (void)initLayout
{
// 创建UICollectionView的样式布局
WaterFlowLayout *water = [[WaterFlowLayout alloc] init];
CGFloat width = ([UIScreen mainScreen].bounds.size.width - 40) / 3;
water.itemSize = CGSizeMake(width, width);
// 设置内边距
water.sectionInsets = UIEdgeInsetsMake(10, 10, 10, 10);
// 设置间距
water.spacing = 10;
// 设置有多少列
water.numberOfColumn = 3;
// 设置代理
water.delegate = self;
// 布局UICollectionView
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:water];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
self.collectionView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.collectionView];
}
// 读取数据
- (void)loadData
{
// 第一步:获取文件路径
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"json"];
// 第二步:根据路径读取数据,转为NSData对象
NSData *data = [NSData dataWithContentsOfFile:filePath];
// 第三步:根据json格式解析数据
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
// 第四步:遍历数据,将数据转化成model对象
for (NSDictionary *dict in array) {
DataModel *dataModel = [[DataModel alloc] init];
[dataModel setValuesForKeysWithDictionary:dict];
[self.allDataArr addObject:dataModel];
}// NSLog(@"%@", self.allDataArr);
}// 返回多少个分区
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
// 设置每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.allDataArr.count;
}
// 返回每一个item的cell对象
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
RootCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier_cell forIndexPath:indexPath];
// 设置cell数据
DataModel *model = self.allDataArr[indexPath.row];
[cell.photoView sd_setImageWithURL:[NSURL URLWithString:model.thumbURL]];
cell.backgroundColor = [UIColor orangeColor];
return cell;
}
// 实现代理方法 返回每一个item的高度
- (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexpath
{
DataModel *model = self.allDataArr[indexpath.row];
CGFloat width = ([UIScreen mainScreen].bounds.size.width - 40) / 3;
CGFloat height = model.height / model.width * width;
return height;
}
@end
在WaterFlowLayout.h
#import
- (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexpath;
@end
@interface WaterFlowLayout : UICollectionViewLayout// item的大小,需要根据这个获取宽度
@property (nonatomic, assign) CGSize itemSize;// 内边距的设置
@property (nonatomic, assign) UIEdgeInsets sectionInsets;// item的间距(这里水平方向和垂直方向间距一样)
@property (nonatomic, assign) CGFloat spacing;// 列数
@property (nonatomic, assign) NSInteger numberOfColumn;// 设置代理,用于获取每个Item的高度
@property (nonatomic, weak) id
@end
在WaterFlowLayout.m
#import "WaterFlowLayout.h"@interface WaterFlowLayout ()
// 声明私有属性
// 一共有多少个item@property (nonatomic, assign) NSInteger numberOfItems;
// 保存计算好的每一个item的信息
@property (nonatomic, strong) NSMutableArray *itemAttributes;
// 保存每一列的高度
@property (nonatomic, strong) NSMutableArray *columnHeights;
// 声明私有方法// 找到当前最长列
- (NSInteger)indexOflLongestColumn;
// 找到当前最短列
- (NSInteger)indexOfShortestColumn;
@end
@implementation WaterFlowLayout
// 懒加载
- (NSMutableArray *)itemAttributes
{
if (!_itemAttributes) {
_itemAttributes = [NSMutableArray array];
}
return _itemAttributes;
}
- (NSMutableArray *)columnHeights
{
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
}
// 找到最长列
- (NSInteger)indexOflLongestColumn
{
// 记录最长的下标
NSInteger index = 0;
// 记录最长列的高度
CGFloat length = 0;
for (int i = 0; i < self.columnHeights.count; i++) {
// 将数组中的对象转为基本数值
CGFloat currentLength = [self.columnHeights[i] floatValue];
if (currentLength > length) {
length = currentLength;
index = i;
}
}
return index;
}
// 找到最短列
- (NSInteger)indexOfShortestColumn
{
// 记录最短的下标
NSInteger index = 0;
// 记录最短列的高度
CGFloat length = [self.columnHeights[0] floatValue];
for (int i = 0; i < self.columnHeights.count; i++) {
// 将数组中的对象转为基本数值
CGFloat currentLength = [self.columnHeights[i] floatValue];
if (currentLength < length) {
length = currentLength;
index = i;
}
}
return index;
}#pragma mark 重写Layout布局都会走这3个方法// 重写三个方法// 准备布局,在这里计算每个item的frame
- (void)prepareLayout
{
// 拿到一个有多少个item
self.numberOfItems = [self.collectionView numberOfItemsInSection:0];
// 每一列添加一个top高度
for (int i = 0; i < self.numberOfColumn; i++) {
// @() NSNumber字面量创建对象
self.columnHeights[i] = @(self.sectionInsets.top);
}
// 依次为每个item设置位置信息,并存储在数组中
for (int i = 0; i < self.numberOfItems; i++) {
// 找到最短列下标
NSInteger shortestIndex = [self indexOfShortestColumn];
// 计算x 目标x = 内编剧做间距 + (宽 +item间距)* 最短列下标
CGFloat detalX = self.sectionInsets.left + shortestIndex * (self.itemSize.width + self.spacing);
// 找到最短列的高度
CGFloat height = [self.columnHeights[shortestIndex] floatValue];
// 计算Y
CGFloat detalY = height + self.spacing;
// 创建indexPath
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 调用代理方法计算高度
CGFloat itemHeight = 0;
if (_delegate && [_delegate respondsToSelector:@selector(heightForItemAtIndexPath:)]) {
itemHeight = [_delegate heightForItemAtIndexPath:indexPath];
}
// 定义保存位置信息的对象
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// 生成frame
attributes.frame = CGRectMake(detalX, detalY, self.itemSize.width, itemHeight);
// 将位置信息添加到数组中 [self.itemAttributes addObject:attributes];
// 更新这一列的高度
self.columnHeights[shortestIndex] = @(detalY + itemHeight);
}
}// 返回UICollectionView的大小
- (CGSize)collectionViewContentSize
{
// 求最高列的下标
NSInteger longest = [self indexOflLongestColumn];
// 最高列的高度
CGFloat height = [self.columnHeights[longest] floatValue];
// 拿到collectionView的原始大小
CGSize size = self.collectionView.frame.size;
size.height = height + self.sectionInsets.bottom;
return size;
}// 返回每一个Item的信息
- (NSArray
{
return self.itemAttributes;
}
@end
在RootCell.h
#import
@interface RootCell : UICollectionViewCell
@property (nonatomic, strong) UIImageView *photoView;
@end
在RootCell.m
#import "RootCell.h"
@implementation RootCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.photoView = [[UIImageView alloc] init];
[self.contentView addSubview:self.photoView];
}
return self;
}
// 子布局改变
- (void)layoutSubviews
{
self.photoView.frame = self.bounds;
}
@end
在DataModel.h
#import
@interface DataModel : NSObject// json文件图片地址
@property (nonatomic, copy) NSString *thumbURL;// 图片宽
@property (nonatomic, assign) float width;// 图片高
@property (nonatomic, assign) float height;
@end
在DataModel.m
#import "DataModel.h"
@implementation DataModel
// 防止找不到对应key值程序崩溃
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
@end
程序运行效果(瀑布流效果):
- 赞