UICollectionView
来源:互联网 发布:数据迁移整体解决方案 编辑:程序博客网 时间:2024/05/01 09:17
概念
UICollectionView继承自UIScrollView,支持水平和垂直滚动。UICollectionView和UITableView共享API设计,并在UITableView的基础上做了扩展。UICollectionView最强大、同时显著超出UITableView的特色就是其完全灵活的布局结构
UICollectionView也是由dataResource和delegate驱动的,为其显示的子视图集扮演容器,对它们的真实内容毫不知情
###UICollectionView的创建
代码创建collectionView
```objc
// collectionView在初始化时,除了frame,还必须提供collectionView的布局参数
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
```
###UICollectionViewLayout类
创建collectionView时的必须对象参数,决定collectionView的布局方式。一般使用其子类UICollectionViewFlowLayout的对象作为参数,对collectionView的Cell进行流水布局
###使用UICollectionView的步骤
1. storyboard中拖拽UICollectionView控件到View中,或者代码创建UICollectionView控件,或者拖拽UICollectionViewController
2. 布局UICollectionViewCell
3. 设置UICollectionView的代理对象,实现dataSource数据源方法
4. 为UICollectionView注册Cell类以及重用ID。实现collectionView的数据源方法返回Cell时,根据重用ID去缓存池中找。如果没找到,则根据注册类创建一个
```objc
NSString *ID = @"cell";
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:ID];
```
storyboard中设置collectionView的Cell的尺寸
![](Snip20160113_10.png)
##UICollectionViewFlowLayout布局类型类
collectionView的流水布局参数类。
**flowLayout**:通过一个接一个的放置Cell来建立布局,当需要时,插入横排或竖排的分栏符。通过自定义滚动方向、大小和Cell之间的距离,flow layout也可以在单行或单列中布局Cell
flowLayout的参数
![](Snip20160113_12.png)
###collectionViewCell的三种注册方式
####1 纯代码创建Cell
1. 创建继承自UICollectionViewCell的自定义类,重写父类的initWithFrame方法,创建所需的子控件并进行关联
2. 在viewDidLoad中,调用collectionView的registerClass方法,为自定义Cell类注册重用ID
![](Snip20160114_1.png)
3. 重写自定义Cell类数据模型属性的setter方法,为Cell子控件设置数据
4. 在Cell类的layoutSubviews方法中,设置子控件的frame
**注**:Cell的尺寸,是由collectionView来统一设置,或者通过collectionView的代理方法来确定的
####2 xib创建Cell
1. 创建继承自UICollectionViewCell的自定义类,同时生成xib文件
2. 在xib文件中,设置Cell的尺寸、子控件以及自动布局方式
3. 在viewDidLoad方法中,调用collectionView的registerNib方法注册Cell对应的xib文件以及重用ID。
**如果要在xib中设置Cell的重用ID,则必须与代码中注册的重用ID一致!**
![](Snip20160115_3.png)
4. 重写Cell类的数据模型属性的setter方法,为子控件设置数据
5. Cell的Size与xib中模板的大小无关,由collectionView的layout属性决定
6. 需要使用xib中Cell的尺寸作为实际尺寸时,可以用下面这个方法获取
![](Snip20160115_2.png)
####3 storyboard中使用collectionViewController自带Cell
1. 拖拽一个UICollectionViewController到storyboard面板中
2. 设置控制器的关联类为继承自UICollectionViewController的自定义类
3. 设置控制器自带Cell的模板子控件及其布局信息,注册Cell的重用ID
###设置collectionViewCell尺寸的方式
1. 通过设置collectionView的layout参数对象的itemSize属性来统一设置Cell的尺寸
2. 在storyboard中选中collectionView控件,属性中统一设置Cell的尺寸(storyboard中选中collectionView修改Cell的尺寸,实际修改的就是拖拽collectionView控件时自带flowLayout参数的Cell尺寸属性)
3. 通过实现collectionView的获取Cell尺寸的代理方法,来逐一设置每个Cell的尺寸
4. xib创建的Cell模板,不会有Cell的Size属性。如果没有其它方式设置Cell的Size,将采用默认Size
###collectionView的三个代理协议
* UICollectionViewDelegate
* UICollectionViewDataSource
* UICollectionViewDelegateFlowLayout
###collectionView展示lol英雄实现横屏后正常显示
```objc
// 屏幕将要旋转时,修改layout的itemSize属性,将collectionView重新布局Cell时,自动调整Cell的size
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
// 此时没有转换完毕,不能使用View的size,必须用方法自带的转换后的size参数
self.flowLayout.itemSize = CGSizeMake(size.width - self.flowLayout.sectionInset.left - self.flowLayout.sectionInset.right, 60);
}
```
###疑问:collectionView实现lol英雄展示中,如何实现动态调整Cell的高度?
tableView中,可以根据设置Cell的contentView的底部与某一子控件的参数的约束,同时设置Cell预估行高与尺寸自动调整参数来实现Cell高度自动调整的要求。
而collectionView没有自动调整高度的参数,其flowLayout参数中也没有相关参数。怎么实现要求?
##collectionView图片无限滚动实现的思想
* collectionView只有3个Cell。每次滚动结束后,都让collectionView以非动画方式,滚回到第一个Cell(Cell从序数0开始)上,以保证每次拖拽滚动结束后,collectionView依然可以滚动
* 通过监听collectionView的滚动方向对应修改图片的全局索引值,每次collectionView自动滚回到第一个Cell,在collectionView创建Cell时,将对应的内容设置给collectionView的第一个Cell
* collectionView刷新Cell前关闭动画,刷新后重新开启动画
```objc
// 监听collectionView的滚动事件
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
// 根据滚动结束后,collectionView的内容偏移值,计算出要显示的图片索引的变化
CGFloat offset = self.collectionView.contentOffset.x;
NSInteger offsetPage = (offset / self.view.frame.size.width) - 1;
// 未翻页,什么都不做
if (!offsetPage) {
return;
}
// +总页数对总页数取模,保证不越界
self.imageIndex = (self.imageIndex + offsetPage + self.images.count) % self.images.count;
// 每次滚动结束后,重新让collectionView滚动到中间Cell上
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:1 inSection:0];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
// 必须要刷新Cell。
// 每次新Cell出现时,都调用了代理返回Cell的方法,刷新了数据。为什么会出现Cell显示图片错误的问题?
// 这个显示bug是由于collectionView多线程任务执行完成顺序先后造成的
[UIView setAnimationsEnabled:NO];
[self.collectionView reloadItemsAtIndexPaths:@[indexPath]];
[UIView setAnimationsEnabled:YES];
}
```
###-viewDidAppear:
程序启动viewDidLoad方法执行完时,collectionView的代理方法还没有调用。此时设置collectionView的Cell的相关属性和数据,是无意义的。必须要等到View被显示之后(所有的相关方法都调用执行完毕),才可以设置collectionView的Cell的相关参数和属性
---
##瀑布流布局实现思路及具体步骤
###思路
1. 定宽不定高:列宽相等,每一个item的高度由内容决定。
2. 每列下面一个Cell的Y轴起点,由上一个Cell的底部决定
3. collectionView的内容尺寸,由Cell最大的Y值决定
###实现步骤
1. 为collectionView注册xib模板的Cell,设置Cell的子控件及约束
2. 实现collectionView的数据源协议方法,布局Cell
3. 自定义继承自UICollectionViewFlowLayout类的子类,重写父类的某些方法,手动计算每一个Cell对应的属性
4. 设置collectionView的flowLayout参数关联新建的自定义类
5. storyboard中给collectionView添加footerView,并关联自定义类,实现数据源方法中返回页脚View方法
6. 实现新增数据并刷新的方法,当FooterView 被创建后,自动执行并刷新数据
$$控制器类相关属性$$
```objc
@property (nonatomic, strong) NSMutableArray *goods;
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@property (weak, nonatomic) IBOutlet ASWaterFlowLayout *flowLayout;
@property (nonatomic, weak) ASFooterView *footerView;
```
###自定义flowLayout类计算每个Cell的frame属性
$$属性$$
```objc
@property (nonatomic, assign) NSInteger columnCount; // 排布列数
@property (nonatomic, weak) id<ASWaterFlowLayoutDelegate> delegate; // 获取Cell高度的代理对象
```
$$代理协议$$
```objc
@protocol ASWaterFlowLayoutDelegate <NSObject>
// 根据Cell序数及宽度,获取Cell高度
- (CGFloat)waterFlowLayoutGetCellHeight:(ASWaterFlowLayout *)waterFlowLayout
withIndex:(NSInteger)index andCellWidth:(CGFloat)cellWidth;
@end
```
$$延展属性$$
```objc
@property (nonatomic, strong) NSMutableArray *arrayAttrs; // 自定义保存每个Cell属性的数组
@property (nonatomic, strong) NSMutableDictionary *dictMaxY; // 自定义保存每行当前Cell总高度的字典
@property (nonatomic, copy) NSString *keyMaxY; // 字典中最大Y值对应的key
@property (nonatomic, copy) NSString *keyMinY; // 字典中最小Y值对应的key
```
$$字典和数组的懒加载$$
```objc
- (NSMutableArray *)arrayAttrs{
if (_arrayAttrs == nil) _arrayAttrs = [NSMutableArray array];
return _arrayAttrs;
}
- (NSMutableDictionary *)dictMaxY{
if (!_dictMaxY) _dictMaxY = [NSMutableDictionary dictionary];
return _dictMaxY;
}
```
$$重写prepareLayout方法$$
```objc
- (void)prepareLayout{
[super prepareLayout];
// 初始化字典,让字典拥有三组键值
for (int i=0; i<self.columnCount; i++) {
NSString *key = [NSString stringWithFormat:@"%d", i];
self.dictMaxY[key] = @"0";
}
// 每次刷新数据准备布局collectionView时,清除所有Cell的layout属性
[self.arrayAttrs removeAllObjects];
// 计算所有Cell的属性
// 获取collectionView第0组的总Cell数
NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
for (int i=0; i<cellCount; i++) {
// 获取当前Cell所在的坐标
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
// 获取对应Cell的属性数据
UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath];
// 将当前Cell的属性信息添加到Cell属性数组中
[self.arrayAttrs addObject:attr];
}
// 计算footerView的属性
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
UICollectionViewLayoutAttributes *attrFooter = [UICollectionViewLayoutAttributes
layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:indexPath];
// 根据内容最大Y值,设置footerView的属性
NSString *maxHeight = self.keyMaxY;
attrFooter.frame = CGRectMake(0, [self.dictMaxY[maxHeight] floatValue],
self.collectionView.frame.size.width, self.footerReferenceSize.height);
[self.arrayAttrs addObject:attrFooter];
}
```
**注:**属性数组中,可以重复为指定indexPath的Cell添加多个不同的属性实例,最后布局时,以最后一个被添加进数组的属性实例为准。但是,不可以添加多于1个footerView的属性实例!新添加进的footerView的属性实例不会在布局时替换旧值,而是直接冲突报错!
![](Snip20160117_2.png)
$$计算每个item的frame并返回$$
```objc
// 计算Cell的属性。控制器每布局一个Cell,都会来调用一次这个方法
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
// 计算每一个Cell的frame
CGFloat contentW = self.collectionView.frame.size.width - self.sectionInset.left -
self.sectionInset.right - self.minimumInteritemSpacing * (self.columnCount - 1);
// 计算每个Cell的宽,代理获取对应的高
CGFloat cellW = contentW / self.columnCount;
CGFloat cellH = [self.delegate waterFlowLayoutGetCellHeight:self withIndex:indexPath.item andCellWidth:cellW];
// 将Cell布局在Y值最小的列
NSInteger col = [self.keyMinY integerValue];
CGFloat cellX = self.sectionInset.left + col * (cellW + self.minimumInteritemSpacing);
NSString *keyCol = [NSString stringWithFormat:@"%ld", col];
CGFloat cellY = [self.dictMaxY[keyCol] floatValue];
// 更新字典中布局Cell所在列的最大高度
CGFloat maxY = cellY + cellH + self.minimumInteritemSpacing;
self.dictMaxY[keyCol] = [NSString stringWithFormat:@"%f", maxY];
// 修改对应Cell在数组中保存的属性值
UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attr.frame = CGRectMake(cellX, cellY, cellW, cellH);
// 返回Cell的属性值
return attr;
}
```
$$返回可视范围内所有Cell的布局属性泛型数组$$
```objc
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.arrayAttrs;
}
```
$$返回collectionView的contentSize$$
```objc
- (CGSize)collectionViewContentSize{
return CGSizeMake(0, [self.dictMaxY[self.keyMaxY] floatValue] + self.footerReferenceSize.height);
}
```
$$获取字典中最大/小Y值对应的key$$
```objc
- (NSString *)keyMaxY{
// 假定最大的key为第0组键值的key
__block NSString *keyMaxY = @"0";
// block方式遍历字典,比较Y值,获取最大的key
[self.dictMaxY enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull value, BOOL * _Nonnull stop) {
if ([self.dictMaxY[keyMaxY] floatValue] < [value floatValue]) {
keyMaxY = key;
}
}];
return keyMaxY;
}
```
###刷新数据功能模块
$$更新数据数组$$
类似于懒加载,根据新数据plist文件,构建模型数组,数组中的模型元素添加到原数据数组中
$$滚出FooterView之后的刷新数据事件$$
```objc
// 监听collectionView的滚动位置。当footerView被显示出来后,执行这个方法中的内容
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
// FooterView没有出现或者指示器还在旋转时,返回
if (!self.footerView || [self.footerView.activityIndicator isAnimating]) return;
[self.footerView.activityIndicator startAnimating]; // 指示器开始动画
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self reloadData]; // 更新数据数组
[self.collectionView reloadData]; // collectionView刷新数据
[self.footerView.activityIndicator stopAnimating]; // 指示器停止动画
self.footerView = nil; // 释放FooterView
});
}
```
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- UICollectionView
- 第四次其他
- Abobe Flash cs6 和 Abobe Flash Builder4.6 破解和许可证过期
- Flume 抓取日志文件存入MySQL中
- php中eval函数笔记
- JAVA - 1、Win10 Eclipe Java 环境配置及 代码自动补全 设置
- UICollectionView
- 数据库增删改查语句
- HDU-3486 Interviewe
- 工作前5年决定你一生的财富
- iOS关于图片点到像素转换之杂谈
- 排座位
- Java并发编程:Lock
- thinkphp整合系列之phpqrcode生成二维码
- 详细讲解UIKit性能调优(续)