#前言:
- 在做聊天窗口的时候需要使用collectionView,所以作个demo来学习collectionView的使用。
1.今天为了用collectionView来写一个聊天界面所以去写了一个collectionView的简单demo。
#出现问题:
想要的效果是这样子的:
但却出现这样子的效果:(bad effect)
关键代码代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"testCell"];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 13;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 7;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"testCell" forIndexPath:indexPath];
_image = [UIImage imageNamed:@"yes"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(cell.frame.size.width/4, cell.frame.size.height/4, cell.frame.size.width/2, cell.frame.size.height/2)];
[imageView setImage:_image];
if (indexPath.section %2 == 0) {
[cell setBackgroundColor:[UIColor blueColor]];
}else {
[cell setBackgroundColor:[UIColor greenColor]];
}
[cell.contentView addSubview:imageView];
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%ld",indexPath.section % 2);
if (indexPath.section % 2 == 0) {
return CGSizeMake(50, 50);
}
return CGSizeMake(100, 100);
}
#问题解决过程:
根据bad effect
显示的内容,可以推测:关键问题应该是view的重用我没做好。
##第一次分析:
认为问题时出现在这里,在每次cell
出现在用户可见的区域时,[cell.contentView addSubview:imageView];
便会在view上添加一个imageView,在每次cell
消失在用户可见的区域时,我并没有imageView移除或者进行重用的操作。
##第一次修改:重新正确利用官方的重用机制
代码如下:(为了描述方便,被注释的部位即为我修改的地方,)
同时我也对storyboard中的cell做了修改,其他没列出来的就表示没有被窝修改
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"testCell" forIndexPath:indexPath];
// _image = [UIImage imageNamed:@"yes"];
// UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(cell.frame.size.width/4, cell.frame.size.height/4, cell.frame.size.width/2, cell.frame.size.height/2)];
// [imageView setImage:_image];
if (indexPath.section %2 == 0) {
[cell setBackgroundColor:[UIColor blueColor]];
}else {
[cell setBackgroundColor:[UIColor greenColor]];
}
// [cell.contentView addSubview:imageView];
return cell;
}
第一次修改后的结果:
这时候居然没东西,空的。
第二次分析:
然后我就去看官方文档
里面有这么一段:Before you call either of these methods, you must tell the collection view how to create the corresponding view if one does not already exist. For this, you must register either a class or a nib file with the collection view. For example, when registering cells, you use the registerClass:forCellWithReuseIdentifier: or registerNib:forCellWithReuseIdentifier: method. As part of the registration process, you specify the reuse identifier that identifies the purpose of the view. This is the same string you use when dequeueing the view later.
我马上去看我的代码,确实也有这句关键的代码。registerClass:forCellWithReuseIdentifier:
问题应该就出现在这句上面,这里一定有我没掌握好或者说是错误理解的关键性知识点。
于是我搜索关键字:forCellWithReuseIdentifier
在SOF上找到了下面的解答.
从提问者的描述中:
I get a black collection view. When I delete it everything works fine.
第二次修改代码:
我们知道这个人遇到的是和我们一样的问题,于是我也把下面这段代码注释了。
//[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"testCell"];
第一次修改后的结果:
居然显示正确了。
秉着稣的教诲,知其然还需知其所以然。
#尝试分析问题原因:
在第一次修改代码前,之所以能显示图片是我直接在cell上添加了图片。并没有重用可言。
在第一次修改代码后,之所以不能显示图片,是我用过[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"testCell"];
注册了一个重用的cell类,但在实际执行的过程中即:在cellForItemAtIndexPath
中的UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"testCell" forIndexPath:indexPath];
cell并非我之前注册的东西(并不是同一个,是让identifier是一样的).
#尝试利用实验来证明自己的分析:
我马上写了2个自定义的CollectionViewCell
并做了如下的修改
[self.collectionView registerClass:[TestCollectionViewCell class] forCellWithReuseIdentifier:@"testCell"];
imageCollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"testCell" forIndexPath:indexPath];
从图中可以看到,虽然我imageCollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"testCell" forIndexPath:indexPath];
是赋值给imageCollectionViewCell类型的变量cell但它实际是registerClass的TestCollectionViewCell
。从而说明这2个不是同一个东西.
为了检验自己的想法是不是对的,我们来看看之前的在SOF上找到的解答.
从回答者的描述中,
If you've already created your UICollectionView in Storyboard, connected your dataSource and delegate, and you have added all of the required methods:
numberOfItemsInSection
numberOfSectionsInCollectionView (not a required method -
refer to this comment)
cellForItemAtIndexPath
Then the registerClass / registerCell method isn't required. However, if you need to reuse a view, data, or cells then you should use those methods so that iOS can populate your UICollectionView as needed. This can also be done in your Storyboard by setting the Prototype Cell (the same principal as the registerClass method.
Also, if you're looking for a good explanation on what registerCell does and how to use it check out
this linkand scroll to the bottom section titled "Cell and View Reuse."
我们可以知道使用storyboard的话(做完必要的配置)的效果就相当于已经写了registerClass
方法。
从他们的讨论中
Thanks @RazorSharp. However, would just like to point out numberOfSectionsInCollectionView: is not a 'required method'. As per
Apple Documentation, If you do not implement this method, the collection view uses a default value of 1. – So Over It May 17 '13 at 2:38
@SoOverIt Good point! I had forgotten that, please check out my edit. – Sam May 18 '13 at 1:19
The main point is that you can connect a prototype cell directly in a storyboard, which fulfills the same role as calling registerClass:.... Memory usage or cell reuse don't really have anything to do with this. A collection view never loads cells into memory that aren't on screen, and if the cell identifier wasn't actually registered, the line where he dequeues a cell would crash. – omz May 18 '13 at 1:39
Perfect Answer!! save lots of my time Thank you SAM!! – Nitya Dec 10 '13 at 10:48
Agree with
RazorSharp’sanswer and wanted to point out that that key phrase for me in the
Techtopia linkis:
If the cell class was written in code, the registration is performed using the registerClass: method of UICollectionView, otherwise use registerNib
如果这个cell类是用代码写,那么这个registerClass
那么就会用registerClass否则就是用registeNIB。意思就是如果你用storyboard写,然后又在代码中用了registerClass
cell的重用就会是代码的cell类的而不是storyboard的cell类。
那么现在我们来this link看看registerClass到底做了些什么?以及cell的重用是怎么一回事。
没有全部看完,就挑了Cell and View Reuse这段来看。
registerClass就是用来注册cell到重用queue中,当每次请求cell的时候就会从queue中查找是否有已经注册好的cell可以使用,如果有则取出来重新利用,如果没有则创建一个新。解释的还不是很清楚,下次补上。
#遗留的问题
为什么使用代码自定义一个cell是使用下面这种方式来创建:
@interface imageCollectionViewCell : UICollectionViewCell
@property (nonatomic, weak) UIImageView *imageView;
@end
#import "imageCollectionViewCell.h"
@implementation imageCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[imageView setImage:[UIImage imageNamed:@"first"]];
[self.contentView addSubview:imageView];
self.imageView = imageView;
}
return self;
}
@end
@property (nonatomic, weak) UIImageView *imageView;
为什么还要写下面的代码以后在table中[cell.imageView setImage:[UIImage imageNamed:@"yes"]];
才能正常显示所需要的图片呢?
self.imageView = imageView;
下面是对遗留问题的解释:
@implementation imageCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// 为imageView设置初始图片
UIImageView *defaultImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 25, 25)];
[defaultImageView setImage:[UIImage imageNamed:@"first"]];
[self.contentView addSubview:defaultImageView];
// 为了初始化self.imageView防止为nil
self.imageView = defaultImageView;
}
return self;
}