【iOS】UICollectionView

打印 上一主题 下一主题

主题 1041|帖子 1041|积分 3123


前言

UICollectionView是iOS6之后引入的一个新的UI控件,它和UITableView有着诸多的相似之处,其中很多代理方法都非常雷同。简单来说,UICollectionView是比UITbleView更加强盛的一个UI控件,有如下几个方面:
1、支持程度与垂直结构
2、通过layout设置的方式进行结构
3、CollectionView中Item的巨细与位置可以自由定义
4、通过layout结构回调的代理方法,可以动态的定制每个item的巨细和collection的大体结构属性
5、更加强盛一点,完全自定义一套layout结构方案,可以实现意想不到的效果
一、实现简单九宫格结构

我们先来看一下简单九宫格的实现效果:

实现步调:
1、创建结构类并为其各个属性赋值
2、将自定义的结构类作为参数传递给UICollectionView的初始化方法- (instancetype)initWithFrameCGRect)frame collectionViewLayoutUICollectionViewLayout *)layout NS_DESIGNATED_INITIALIZER;
3、设置代理,实现协议函数
  1. //创建一个layout布局类
  2. UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc]init];
  3. //设置布局方向为垂直流布局
  4. layout.scrollDirection = UICollectionViewScrollDirectionVertical;
  5. //设置每个item的大小为100*100
  6. layout.itemSize = CGSizeMake(100, 100);
  7. //创建collectionView 通过一个布局策略layout来创建
  8. UICollectionView * collect = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:layout];
  9. //代理设置
  10. collect.delegate=self;
  11. collect.dataSource=self;
  12. //注册item类型 这里使用系统的类型
  13. [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
  14.    
  15. [self.view addSubview:collect];
复制代码
这里有一点需要留意,collectionView在完成代理回调前,必须注册一个cell,雷同如下:
  1. [collect registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellid"];
复制代码
这便是collectionView与tableView不同的一点,tableView除了注册cell的方法外,还可以通过临时创建来做:
  1. //tableView在从复用池中取cell的时候,有如下两种方法
  2. //使用这种方式如果复用池中无,是可以返回nil的,我们在临时创建即可
  3. - (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
  4. //6.0后使用如下的方法直接从注册的cell类获取创建,如果没有注册 会崩溃
  5. - (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);
复制代码
在UICollectionView中,注册cell是必须的,由于UICollectionView没有默认的cell范例,而是完全依靠于开辟者提前注册cell类或nib以供后续利用。
在UITableView中,历史上早期版本的iOS SDK并没有引入注册机制。在那些版本中,你只需在cellForRowAtIndexPath:代理方法中创建或重用cell即可。而且如果你没有注册cell,UITableView会尝试利用默认的UITableViewCell来呈现内容。
   以是我们可以知道由于UITableView引入的版本较早,当时还未引入注册机制,在当时只需在cellForRowAtIndexPath方法中创建或者重用cell即可,而UICollectionView是在iOS
6.0引入的,相对较新。为了支持更灵活的结构和更复杂的cell范例,Apple在引入时就采取了注册机制。
  上面的设置完成后我们再实现几个与tableview雷同的代理方法
另外有一点细节:
我们在设置结构方式的时候设置了垂直结构:
  1. layout.scrollDirection = UICollectionViewScrollDirectionVertical;
  2. //这个是水平布局
  3. //layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
复制代码
这样可以实现当我们的第一行充满后会主动进行第二行的结构
二、UICollectionView中的常用方法和属性

1.UICollectionViewFlowLayout相干属性

(1)初始化cell样式
  1. UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
复制代码
(2)结构类的方向
  1. layout.scrollDirection = UICollectionViewScrollDirectionVertical;
复制代码
  UICollectionViewScrollDirectionVertical 竖向排布
UICollectionViewScrollDirectionHorizontal 横向排布
  (3)设置item的巨细
  1. layout.itemSize = CGSizeMake(120, 100);
复制代码
(4)设置item的上下间距
  1. layout.minimumLineSpacing = 100;
复制代码
(5)设置item的左右间距
  1. layout.minimumInteritemSpacing = 10;
复制代码
(6)设置item的内间距
  1. layout.sectionInset = UIEdgeInsetsMake(5, 5, 5, 5);
复制代码
2.UICollectionView相干属性

(1)UICollectionView实例化对象和巨细
  1. self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
复制代码
(2)革新对应的列
  1. [self.collectionView performBatchUpdates:^{
  2.     [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
  3. } completion:nil];
复制代码
三、协媾和代理方法:

(1)遵守的协议:
<UICollectionViewDelegate, UICollectionViewDataSource>
(2)代理方法:
UICollectionViewDelegate协议
该协议用来设置和处理collectionView的功能和一些逻辑,全部的方法都是可选实现。
是否允许某个item的高亮,返回NO,则不能进入高亮状态


  • (BOOL)collectionViewUICollectionView *)collectionView shouldHighlightItemAtIndexPathNSIndexPath *)indexPath;
    当item高亮时触发的方法
  • (void)collectionViewUICollectionView *)collectionView didHighlightItemAtIndexPathNSIndexPath *)indexPath;
    竣事高亮状态时触发的方法
  • (void)collectionViewUICollectionView *)collectionView didUnhighlightItemAtIndexPathNSIndexPath *)indexPath;
    是否可以选中某个item,返回NO,则不能选中
  • (BOOL)collectionViewUICollectionView *)collectionView shouldSelectItemAtIndexPathNSIndexPath *)indexPath;
    是否可以取消选中某个item
  • (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
    已经选中某个item时触发的方法
  • (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
    取消选中某个item时触发的方法
  • (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
    将要加载某个item时调用的方法
  • (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);
    已经展示某个item时触发的方法
  • (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
    已经展示某个头尾视图时触发的方法
  • (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
    UICollectionView进行重新结构时调用的方法
  • (nonnull UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;
UICollectionViewDataSource协议
必须实现的方法:
设置每个分区的item数


  • (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
    设置返回每个item的属性
  • (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
    可选实现的方法:
    设置分区数,固然这个方法是可选的,一样平常我们都会去实现,不去设置它的话,它的默认值为1
  • (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
    对头视图或者尾视图进行设置
  • (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
    设置某个item是否可以被移动,返回NO则不能移动
  • (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
    移动item的时候,会调用这个方法
  • (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;
四、九宫格式的结构进行升级

偶尔候一个简单的九宫格并不能满意我们的需求,偶尔我们需要item展现不同的巨细
  1. //设置每个item的大小,双数的为50*50 单数的为100*100
  2. -(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
  3.     if (indexPath.row%2!=0) {
  4.         return CGSizeMake(50, 50);
  5.     }else{
  6.         return CGSizeMake(100, 100);
  7.     }
  8. }
复制代码

五、实现瀑布流结构

实现思绪

UIcollectionView的精髓是UICollectionViewLayout,UICollectionViewLayout决定了我们的UIcollectionView是怎样显现在我们的界面上的,为了实现我们的自定义瀑布流式结构,我们需要在子类里重写生成结构的方法,创建我们自己需要的结构
实现原理

   在实现该功能之前,我们先相识一下 UICollectionView 的结构过程,它与结构对象之间的关系是一种协作的关系,当
UICollectionView 需要一些结构信息的时候,它会去调用结构对象的一些函数,这些函数的执行是有肯定的次序的,如图所示:

  以是我们的创建的继承自UICollectionViewLayout的子类实现以下方法:
  1. - (void)prepareLayout;//进行提前创建布局
复制代码
  1. - (CGSize)collectionViewContentSize//返回内容画布的大小
复制代码
  1. - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
  2. //返回rect中所有元素的布局属性,返回的是一个数组
复制代码
  1. - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
  2. //返回对应的indexPath的位置的cell的布局属性。
复制代码
  重写 - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;方法返回对应indexPath的位置的追加视图的结构属性,如果没有就不消重载
重写 - (nullable UICollectionViewLayoutAttributes )layoutAttributesForDecorationViewOfKind:(NSString)elementKind atIndexPath:(NSIndexPath *)indexPath;方法返回对应indexPath的位置的装饰视图的结构属性,如果没有也不需要重载
重写 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;当边界发生改变时,是否应该革新。
  由此我们可以知道如果我们想要实现自定义瀑布流,必须需要对以下方法进行重写

其中我们在prepareLayout方法进行结构前的准备,其重要有两个作用:
初始化结构参数: 在 prepareLayout 中,你可以进行一些初始化工作,例如盘算和缓存用于结构的参数。这大概包括盘算每个单位格的巨细、盘算行列的数目、初始化用于存储结构属性的数据结构等。
盘算并缓存结构属性: 你可以在 prepareLayout 中盘算并缓存聚集视图中全部单位格的结构属性(UICollectionViewLayoutAttributes)。这样,当聚集视图需要显示或进行交互时,可以直接访问缓存的结构属性,而不需要在运行时动态盘算。
而- (CGSize)collectionViewContentSize决定了聚集视图可以滚动的范围,即聚集视图可以在其内容地区内滚动的最大范围。在自定义结构中,你需要根据结构的详细逻辑来盘算内容尺寸。这个尺寸通常是在全部的结构属性(UICollectionViewLayoutAttributes)被盘算之后确定的,以确保可以或许容纳全部的元素。
(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect方法则是用于返回在给定矩形地区内的全部视图的结构属性(UICollectionViewLayoutAttributes 实例)数组
代码

下面给出各个函数的代码:
prepareLayout:
  1. //数组的相关设置在这个方法中
  2. //布局前的准备 会调用这个方法
  3. - (void)prepareLayout {
  4.     [super prepareLayout];
  5.     self.attributeArray = [NSMutableArray array]; // 使用实例变量,而不是全局变量
  6.    
  7.     // 设置为静态的2列
  8.     // 计算每一个item的宽度
  9.     CGFloat itemWidth = (self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing) / 2;
  10.     // 定义数组保存每一列的高度
  11.     // 这个数组的主要作用是保存每一列的总高度,这个样在布局时,我们可以始终将下一个item放在最短的列下面
  12.     // prepareLayout 中
  13.     self.columnHeights = [NSMutableArray arrayWithObjects:@(self.sectionInset.top), @(self.sectionInset.top), nil];
  14.     // itemCount是外界传进来的item的个数 遍历来设置每一个item的布局
  15.     for (int i = 0; i < _itemCount; i++) {
  16.         // 设置每一个item的位置等相关属性
  17.         NSIndexPath *index = [NSIndexPath indexPathForItem:i inSection:0];
  18.         // 创建一个布局属性类, 通过indexPath来创建
  19.         UICollectionViewLayoutAttributes *attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:index];
  20.         // 随意一个高度 在60-260之间
  21.         CGFloat height = arc4random() % 200 + 60;
  22.         // 哪一行高度小 则放到哪一列下面
  23.         // 标记最短的列
  24.         NSInteger shortestColumn = [_columnHeights[0] doubleValue] < [_columnHeights[1] doubleValue] ? 0 : 1;
  25.         // 将新的item高度加入到短的一列
  26.         _columnHeights[shortestColumn] = @([_columnHeights[shortestColumn] doubleValue] + height + self.minimumLineSpacing);
  27.         // 设置item的位置
  28.         attris.frame = CGRectMake(self.sectionInset.left + (self.minimumInteritemSpacing + itemWidth) * shortestColumn,[_columnHeights[shortestColumn] doubleValue] - height - self.minimumLineSpacing,itemWidth, height);
  29.         [self.attributeArray addObject:attris];
  30.     }
复制代码
这里的sectionInset的top与bottom让笔者理解了一段时间,这里用一张图解释一下:

(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect:
  1. //返回布局数组
  2. - (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
  3.     return _attributeArray;
  4. }
复制代码
- (CGSize)collectionViewContentSize:
  1. - (CGSize)collectionViewContentSize {
  2.     // collectionView的contentSize的高度等于所有列高度中最大的值
  3.     CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
  4.     for (NSInteger i = 1; i < 2; i++) {
  5.         CGFloat columnHeight = [self.columnHeights[i] doubleValue];
  6.         if (maxColumnHeight < columnHeight) {
  7.             maxColumnHeight = columnHeight;
  8.         }
  9.         NSLog(@"%f", maxColumnHeight);
  10.     }
  11.     return CGSizeMake(0, maxColumnHeight);
  12. }
复制代码
(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath:
  1. //返回每个item
  2. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  3.     UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
  4.    
  5.     cell.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 255.0 green:arc4random() % 255 / 255.0 blue:arc4random() % 250 / 250.0 alpha:1];
  6.     return cell;
  7. }
复制代码
调用顺序

对于UICollectionView,函数调用顺序为

  • prepareLayout:
    调用时机: 在结构发生变化之前,比如初始化时、调用 reloadData、invalidateLayout 等方法时。
    作用: 进行一些结构的准备工作。
  • collectionViewContentSize:
    调用时机: 在需要盘算聚集视图内容尺寸时,比如初始化时、结构发生变化时。
    作用: 返回整个聚集视图内容的尺寸。
  • layoutAttributesForElementsInRect::
    调用时机: 在需要显示或更新某个地区内的视图时,比如滚动时、结构发生变化时。
    作用: 返回给定矩形地区内全部视图的结构属性。
  • (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    调用时机:创建item时调用
    作用:创建和设置单位格
其中prepareLayout方法只会调用一次,之后在往下滑动UICollectionView时会不停重复调用其他三种方法
相较于仅能纵向单列数据展示的tableView,UICollectionView允许我们进行更加自由且灵活的结构,而轻松实现这种效果则归功于我们的自定义的UICollectionViewFlowLayout类,我们可以通过重写此类中的一些方法来实现我们单位格的多样结构。
实现步调

由此我们得出自定义结构的实现步调:

  • 创建自定义结构类UICollectionViewFlowLayout
  • 重写结构方法,确定结构数组_attributeArray(重要)
  • 注册自定义结构类 MyCustomFlowLayout *customLayout = [[MyCustomFlowLayout alloc] init]; [self.collectionView setCollectionViewLayout:customLayout];
  • 创建cell
实现效果


总结

通过学习UICollectionView,笔者认为最重要的便是知道我们的UICollectionView是怎样去创建我们的单位格的:
通过利用默认或是自定义的流结构UICollectionViewLayout来实现我们的灵活结构,这便需要我们先去实现我们的结构属性数组,确定怎样结构后程序会去创建我们的cell,然后将cell逐个填入我们的结构数组中=
笔者在这里仅仅浮浅地学习了UICollectionView,如有不敷请不吝指出,而对于UICollectionView的焦点,笔者认为其为确定结构

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

麻花痒

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表