您的位置:首页 > 产品设计 > UI/UE

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

2015-09-28 01:30 543 查看

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更加复杂更加强大,在接下的实践中再为大家讲解。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios开发 ios 体验