多类型UITableViewCell重用的正确姿态

2017-05-10

对于iOS开发者,UITableViewCell的重用是最基本的技能,初学者都应该掌握的。对于它的原理我就不在此啰嗦了,这里我重点说下,如何以正确的姿态来重用多类型的UITableViewCell,正确重用cell不仅仅要重用cell视图,还需要好好重用cell的子视图。你是否做到了呢?

单一类型cell重用

对于简单单一cell的tableView来讲,它的重用我们大多会在代理方法里这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  //设置重用标识符
  static NSString * Identifier = @"MineCell";
  //通过Identifier取到cell
  MineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier];
  //若cell不存在,在进行创建
  if (!cell) {
   cell =[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:   Identifier];
  }
  //用set方法取到对应的model对cell赋值
  [cell cellModel:self.cellModelArr[indexPath.row]];
  return cell;
}

或者使用需要注册cell的写法:

1
2
3
4
5
6
7
8
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  //设置重用标识符
  static NSString *Identifier = @"MineCell";
  MineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath:indexPath];
  //通过协议绑定VM和cell
  cell.bind(self.cellModelArr[indexPath.row]);
  return cell;
}

上面的两种写法都很简单,由于是单一类型的cell重用,不会涉及到子视图的重用,所以没什么可讲的。

多类型cell重用

在很多情况下,UITableView并不是单一的一种cell,而会包含多种cell,每种cell类型样式布局都有明显区别。这样来我们就不能用上面单一的cell重用方式了。 对于多类型cell你当然可以对每个类型的cell创建对应的.h .m文件,并在使用时引入该 cell的头文件即可。但可能是出于偷懒,我更习惯统一处理这些cell。把这些cell都写在一个.h .m文件内,通过对不同类型cell设置对应对cellStyle枚举状态来辨别他们,这样以来,一个文件就足够了。

下面就重点说下单文件下多类型cell的重用。主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * MineCellIdentifier = @"MineCellIdentifier";
//通过注册的cell获取MineTableViewCell
MineTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MineCellIdentifier forIndexPath:indexPath];
//根据不同row或者也可以根据model内容来区分cellStyle
if (indexPath.row == 0) {
//通过setMineCellStyle:方法对cell就行类型赋值,我们会在 setMineCellStyle:方法里对cell就行布局等定制化处理。
cell.mineCellStyle = MineTableViewCellStyleOne;
}else
{
cell.mineCellStyle = MineTableViewCellStyleTow;
}
  //通过协议绑定VM和cell
  cell.bind(self.cellModelArr[indexPath.row]);
return cell;
}

我们一般会在MineTableViewCell.h里定义类似MineTableViewCellStyle的枚举类型,并实现mineCellStyle属性,这样就可以在对应的tableView代理方法里通过setMineCellStyle:方法来针对不同cellStyle布局了。setMineCellStyle:方法内部实现大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
- (void)setMineCellStyle:(MineTableViewCellStyle)mineCellStyle {
_mineCellStyle = mineCellStyle;
//为了避免cell重用引起的子视图错乱,我们会先把cell的子视图给全不移除,下面会在对应的cellStyle内重新创建并添加到cell的contentView内 。
for (UIView *view in self.contentView.subviews) {
[view removeFromSuperview];
}
//对不同的类型进行针对性的布局绘制操作
switch (mineCellStyle) {
case MineTableViewCellStyleOne:
{
//这里省略了布局约束的代码
NSArray *viewArray = @[self.bankNumTitleLb, self.bankNumLb];
for (UIView *view in viewArray) {
[self.contentView addSubview:view];
}
//进行布局操作(此处省略不是本文重点)
}
break;
case MineTableViewCellStyleTow:
{
//这里省略了布局约束的代码
NSArray *viewArray = @[self.phoneNumTitleLb, self.phoneNumTextField];
for (UIView *view in viewArray) {
[self.contentView addSubview:view];
}
//进行布局操作(此处省略不是本文重点)
}
break;
case MineTableViewCellStyleDefault:
{
//这里省略了布局约束的代码
NSArray *viewArray = @[self.verificationCodeTitleLb,
self.phoneNumTextField,self.verificationCodeButton];
for (UIView *view in viewArray) {
[self.contentView addSubview:view];
}
//进行布局操作(此处省略不是本文重点)
}
break;
}
}

到这里也许你觉得一切都没什么问题。但有经验的开发者可能已经看出来问题所在。问题出在setMineCellStyle:方法里的这句代码:

1
2
3
for (UIView *view in self.contentView.subviews) {
[view removeFromSuperview];
}

这句代码很简单,就是前面说到为了解决cell重用时会出现子视图的重用。可是”解决”了子视图的重用问题,那么新问题来了,每次都把子视图移除,重新创建既消耗内存还占用时间,严重会出现滑动出现卡顿现象,而且都删除了重建还能叫重用吗?最多是只是留了个cell的’壳’,里面的’肉’可都是新建的啊。
因此:在通过设置多种CELL枚举类型来实现多样式CELL布局时,要先判断当前重用的CELLSTYLE 和 需要设置的CELLSTYLE是否一致,不一致时,才需要重新布局绘制。
修改后的主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * MineCellIdentifier = @"MineCellIdentifier";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:MineCellOneIdentifier forIndexPath:indexPath];
//获取当前重用cell的类型cellStyle
MineCellStyle cellStyle = cell.mineCellStyle;
switch (indexPath.section) {
case 0:
{
//如果当前重用的cellStyle 和 需要设置的cellStyle不一致时,才进行类型重制!
if (cellStyle != MineTableViewCellStyleOne) {
cell.mineCellStyle = MineTableViewCellStyleOne;
}
   cell.bind(self.cellModelArr[indexPath.row]);
}
break;
case 1:
{
switch (indexPath.row) {
case 0:
{
if (cellStyle != MineTableViewCellStyleTow) {
cell.mineCellStyle = MineTableViewCellStyleTow;
}
   cell.bind(self.cellModelArr[indexPath.row]);
}
break;
case 1:
{
if (cellStyle != MineTableViewCellStyleThree) {
cell.mineCellStyle = MineTableViewCellStyleThree;
}
   cell.bind(self.cellModelArr[indexPath.row]);
}
break;
}
}
break;
}
return cell;
}

这样以来即实现了单文件下实现多cell类型的功能,又完美的解决了多cell的重用及性能问题。但是如果cell比较复杂,后续业务变动大还是建议不同cell单独定义,方便后续拓展维护。