iOS开发小白学习体验-UICollectionView(一)

来源:互联网 发布:java socket 长链接 编辑:程序博客网 时间:2024/05/20 17:40

UICollectionView

    • UICollectionView
      • UICollectionViewLayout
      • 关于重用

最近在逛一些技术论坛的时候,看到过这么一篇文章,说的是iOS9新加入的一些特性。大概的意思就是说iOS9的API中给UICollectionView新添加了一个可以拖拽cell并预测停止点方法,由于笔者写的仓促没有找到那个demo。

当看完那篇文章之后感觉眼前一亮,就像是《红楼梦》中的刘姥姥进大观园一样,满脸写满了好奇和惊喜。但是又因为最近忙着学习巩固前面的东西,今天终于腾出了时间来学习这个控件。

(请原谅我的啰嗦,我想说一下我的学习过程,不想看的朋友可以直接看下面的知识点)

笔者在学习一个新的控件的时候第一步当然是新建一个工程,然后定义一个UICollectionView的对象。但是在创建对象的时候发现一个和一般的控件不太一样的创建方法:
-initWithFrame: collectionViewLayout:
一般来说以苹果的规范比较特殊的方法都是比较重要的方法(这只是个人总结的一个看法,如果不对请指正),但是它要传入一个UICollectionViewLayout的对象,不知道是什么东西就先创建一个匿名的.

UICollectionView *collection = [[UICollectionView alloc]initWithFrame:frame collectionViewLayout:[[UICollectionViewLayout alloc]init]];

创建完对象,进入UICollectionView.h文件看看有什么属性需要什么代理。我们会发现他有两个协议,一个是delegate一个是dataSource。看到这里是不是觉得和UITableView比较像?实际上也是比较像的,所以笔者就很自信的直接遵守协议然后实现必须实现的代理方法。

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{    return 5;}- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{    return 10;}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{    static NSString *ID = @"MyFirstCell";    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];    if (cell == nil) {        cell = [[UICollectionViewCell alloc]init];    }    cell.backgroundColor = LYHRandomColor;    return cell;}

但是在运行的时候发现一个问题,我实现了数据源方法但是运行出来什么都没有。然后笔者就在这三个代理方法中打了断点,发现一个很奇怪的事情,就是UICollectionViewCell的这个代理方法无论如何都无法进入。这就让人很费解了,为什么我明明实现了这个代理方法,但是在运行的时候就不进这个代理方法呢?笔者实在是被弄的不会了,就上网找资料了。王巍@onevcat大神的博客


以下摘自大神的博客:

原来是在创建的时候我传入的是UICollectionViewLayout类型的,苹果提供了一个比较常用的layout是UICollectionViewFlowLayout类型的。

UICollectionViewLayout

UICollectionViewLayout是UICollectionView的精髓,这也是UITableView和UICollectionView最大的不同点。
UICollectionViewLayout可以说是UICollectionView的大脑和中枢,它负责将各个cell、Supplementary View 和 Decoration Views 进行组织,为它们设定各自的属性,包括但不限于:

  • 位置
  • 尺寸
  • 透明度
  • 层级关系
  • 形状
  • 等等等等……
  • Layout决定了UICollectionView是如何显示在界面上的。在展示之前,一般要合成合适的UICollectionViewLayout子类对象,并将其赋予CollectionView的collectionViewLayout属性。

Apple为我们提供了一个最简单的可能也是最常用的默认layout对象,UICollectionViewFlowLayout。Flow Layout 简单的说是一个直线对齐的layout,最常见的Grid View形式即为一种Flow Layout配置。

  • 首先一个重要的属性是itemSize,它定义了每一个item的大小。通过设定itemSize可以全局地改变所有cell的尺寸,如果想要对某个cell制定尺寸,可以使用-collectionView:layout:sizeForItemAtIndexPath:方法。
  • 间隔 可以指定item之间的间隔和每一行之间的间隔,和size类似,有全局属性,也可以对每一个item和每一个section做出设定:
    • @property (CGSize) minimumInteritemSpacing
    • @property (CGSize) minimumLineSpacing
    • -collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
    • -collectionView:layout:minimumLineSpacingForSectionAtIndex:
  • 滚动方向 由属性scrollDirection确定scroll view的方向,将影响Flow Layout的基本方向和由header及footer确定的section之间的宽度
    • UICollectionViewScrollDirectionVertical
    • UICollectionViewScrollDirectionHorizontal
  • Header和Footer尺寸 同样地分为全局和部分。需要注意根据滚动方向不同,header和footer的高和宽中只有一个会起作用。垂直滚动时section间宽度为该尺寸的高,而水平滚动时为宽度起作用
    • @property (CGSize) headerReferenceSize
    • @property (CGSize) footerReferenceSize
    • -collectionView:layout:referenceSizeForHeaderInSection:
    • -collectionView:layout:referenceSizeForFooterInSection:
  • 缩进
    • @property UIEdgeInsets sectionInset;
    • -collectionView:layout:insetForSectionAtIndex:

总结:
一个UICollectionView的实现包括两个必要部分:UICollectionViewDataSource和UICollectionViewLayout,和一个交互部分:UICollectionViewDelegate。而Apple给出的UICollectionViewFlowLayout已经是一个很强力的layout方案了。


所以我们知道了在用UICollectionView的时候Layout是一个精髓,只有在设置了一个合适的Layout之后才能显示数据源的。再结合之前打的断点我们可以推断出它的执行流程是这样的(这仅仅是个人的猜测)
先执行:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
再执行:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
然后再去找相关layout的设置,(不论其他的代理方法)如果没有设置layout那么是不会执行相关cell的代理方法的,只有设置了才执行。

那么我们找到原因了代码修改如下

UICollectionViewFlowLayout *layout= [[UICollectionViewFlowLayout alloc]init];    layout.itemSize = CGSizeMake(80, 80);    layout.sectionInset = UIEdgeInsetsMake(20, 20, 20, 20);    layout.minimumInteritemSpacing = 20;    layout.minimumLineSpacing = 20;    self.collextion = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.height) collectionViewLayout:layout];    self.collextion.delegate = self;    self.collextion.dataSource = self;    [self.view addSubview:self.collextion];

再运行,程序崩溃了!这是为什么呢?我们来看看报错的原因

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier MyFirstCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

我们会发现这是因为在重用的时候出错了,但是为什么呢,并没有什么问题啊,根据TableView的经验不能错才对啊

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{    static NSString *ID = @"MyFirstCell";    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];    if (cell == nil) {        cell = [[UICollectionViewCell alloc]init];    }    cell.backgroundColor = LYHRandomColor;    return cell;}

重用队列和tableView没有差别啊,难道中间有一些特别的东西?


以下引用了大神的博客

关于重用

为了得到高效的View,对于cell的重用是必须的,避免了不断生成和销毁对象的操作,这与在UITableView中的情况是一致的。但值得注意的时,在UICollectionView中,不仅cell可以重用,Supplementary View和Decoration View也是可以并且应当被重用的。在iOS5中,Apple对UITableView的重用做了简化,以往要写类似这样的代码:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MY_CELL_ID"];  if (!cell) {    //如果没有可重用的cell,那么生成一个      cell = [[UITableViewCell alloc] init]; } //配置cell,blablabla return cell 

而如果我们在TableView向数据源请求数据之前使用-registerNib:forCellReuseIdentifier:方法为@“MYCELLID”注册过nib的话,就可以省下每次判断并初始化cell的代码,要是在重用队列里没有可用的cell的话,runtime将自动帮我们生成并初始化一个可用的cell。

这个特性很受欢迎,因此在UICollectionView中Apple继承使用了这个特性,并且把其进行了一些扩展。使用以下方法进行注册:

  • -registerClass:forCellWithReuseIdentifier:
  • -registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
  • -registerNib:forCellWithReuseIdentifier:
  • -registerNib:forSupplementaryViewOfKind:withReuseIdentifier:

相比UITableView有两个主要变化:一是加入了对某个Class的注册,这样即使不用提供nib而是用代码生成的view也可以被接受为cell了;二是不仅只是cell,Supplementary View也可以用注册的方法绑定初始化了。在对collection view的重用ID注册后,就可以像UITableView那样简单的写cell配置了:

- (UICollectionView*)collectionView:(UICollectionView*)cv cellForItemAtIndexPath:(NSIndexPath*)indexPath {     MyCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@”MY_CELL_ID”];     // Configure the cell's content     cell.imageView.image = ...     return cell; }

需要吐槽的是,对collection view,取重用队列的方法的名字和UITableView里面不一样了,在Identifier前面多加了Reuse五个字母,语义上要比以前清晰,命名规则也比以前严谨了..不知道Apple会不会为了追求完美而把UITableView中的命名不那么好的方法deprecate掉。


那么我们在创建UICollectionView的时候再加一句话

[self.collextion registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"MyFirstCell"];

所以这时候全部的源代码是这样的:

////  ViewController.m//  UICollectionView////  Created by 厉煜寰 on 15/9/27.//  Copyright © 2015年 SXT. All rights reserved.//#import "ViewController.h"#define LYHRandomColor [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1]@interface ViewController ()<UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout>@property (nonatomic, strong) UICollectionView *collextion;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    UICollectionViewFlowLayout *layout= [[UICollectionViewFlowLayout alloc]init];    layout.itemSize = CGSizeMake(80, 80);    layout.sectionInset = UIEdgeInsetsMake(20, 20, 20, 20);    layout.minimumInteritemSpacing = 20;    layout.minimumLineSpacing = 20;    self.collextion = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.height) collectionViewLayout:layout];    self.collextion.delegate = self;    self.collextion.dataSource = self;    [self.collextion registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"MyFirstCell"];    [self.view addSubview:self.collextion];}- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{    return 5;}- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{    return 10;}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{    static NSString *ID = @"MyFirstCell";    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];    if (cell == nil) {        cell = [[UICollectionViewCell alloc]init];    }    cell.backgroundColor = LYHRandomColor;    return cell;}@end

再运行的时候我们就能把CollectionView显示在界面上了。当然这只是万里长征第一步,CollectionView比TableView更加复杂更加强大,在接下的实践中再为大家讲解。

0 0
原创粉丝点击