在某些業(yè)務(wù)場(chǎng)景下,同一個(gè)UITableView需要支持多種UITableViewCell宠纯《孵铮考慮一下即時(shí)通信的聊天對(duì)話列表,不同的消息類型對(duì)應(yīng)于不同的UITableViewCell衬浑,同一個(gè)tableview往往需要支持多達(dá)10種以上的cell類型捌浩,例如文本、圖片工秩、位置尸饺、紅包等等。一般情況下助币,UITableViewCell往往會(huì)和業(yè)務(wù)數(shù)據(jù)模型綁定浪听,來展示數(shù)據(jù)。根據(jù)不同的業(yè)務(wù)數(shù)據(jù)眉菱,對(duì)應(yīng)不同的cell迹栓。本文將探討如何有效的管理和加載多種類型的cell。
為了方便討論俭缓,假設(shè)我們要實(shí)現(xiàn)一個(gè)員工管理系統(tǒng)克伊。一個(gè)員工包含名字和頭像。如果員工只有名字华坦,則只展示名字愿吹,如果只有頭像,則只展示頭像惜姐,如果既有名字犁跪,又有頭像,則需要既展示頭像又展示名字歹袁。
我們用一個(gè)Person類表示員工坷衍,用三種不同的cell來處理不同的展示情況。
@interface?Person?:?NSObject
@property?(nonatomic,?copy)?NSString?*name;
@property?(nonatomic,?strong)?NSString?*avatar;
@end
/*負(fù)責(zé)展示只有名字的員工*/
@interface?TextCell?:?BaseCell
-?(void)setPerson:(Person?*)p;
@end
/*負(fù)責(zé)展示只有頭像的員工*/
@interface?ImageCell?:?BaseCell
-?(void)setPerson:(Person?*)p;
@end
/*負(fù)責(zé)展示只有既有名字又有頭像的員工*/
@interface?TextImageCell?:?BaseCell
-?(void)setPerson:(Person?*)p;
@end
這三個(gè)類都繼承了BaseCell条舔,BaseCell繼承UITableViewCell
@interface?BaseCell?:?UITableViewCell
-?(void)setPerson:(Person?*)p;
@end
下面我們?cè)赨ITableView的delegate來處理展示Cell
第一次嘗試:
-?(UITableViewCell?*)tableView:(UITableView?*)tableView?cellForRowAtIndexPath:(NSIndexPath?*)indexPath
{
BaseCell?*cell;
NSString?*cellIdentifier;
switch?(p.showtype)?{
case?PersonShowText:
cellIdentifier?=?@"TextCell";
break;
case?PersonShowAvatar:
cellIdentifier?=?@"PersonShowAvatar";
break;
case?PersonShowTextAndAvatar:
cellIdentifier?=?@"PersonShowTextAndAvatar";
break;
default:
break;
}
cell?=?[tableView?dequeueReusableCellWithIdentifier:cellIdentifier];
if?(!cell)?{
switch?(p.showtype)?{
case?PersonShowText:
cell?=?[[TextCell?alloc]?initWithStyle:UITableViewCellStyleDefault?reuseIdentifier:cellIdentifier];
break;
case?PersonShowAvatar:
cell?=?[[ImageCell?alloc]?initWithStyle:UITableViewCellStyleDefault?reuseIdentifier:cellIdentifier];
break;
case?PersonShowTextAndAvatar:
cell?=?[[TextImageCell?alloc]?initWithStyle:UITableViewCellStyleDefault?reuseIdentifier:cellIdentifier];
break;
default:
break;
}
}
[cell?setPerson:p];
return?cell;
}
這段代碼實(shí)現(xiàn)了根據(jù)不同的業(yè)務(wù)模型選取和顯示Cell的邏輯惫叛。但是這段代碼包含了重復(fù)代碼,switch case被調(diào)用了兩次逞刷。我們改進(jìn)一下代碼:
第二次嘗試
-?(UITableViewCell?*)tableView:(UITableView?*)tableView?cellForRowAtIndexPath:(NSIndexPath?*)indexPath
{
BaseCell?*cell;
NSString?*cellIdentifier;
Class?cellClass;
switch?(p.showtype)?{
case?PersonShowText:
cellClass?=?[TextCell?class];
break;
case?PersonShowAvatar:
cellClass?=?[ImageCell?class];
break;
case?PersonShowTextAndAvatar:
cellClass?=?[TextImageCell?class];
break;
default:
cellClass?=?[UITableViewCell?class];
break;
}
cellIdentifier?=?NSStringFromClass(cellClass);
cell?=?[tableView?dequeueReusableCellWithIdentifier:cellIdentifier];
if?(!cell)?{
cell?=?[[cellClass?alloc]?initWithStyle:UITableViewCellStyleDefault?reuseIdentifier:cellIdentifier];
}
[cell?setPerson:p];
return?cell;
}
這次比第一次的代碼看起來好了不少嘉涌,通過一個(gè)通用的Class對(duì)象,來動(dòng)態(tài)生成cell夸浅,避免了兩次調(diào)用switch case的重復(fù)代碼仑最。但是,有沒有更好的實(shí)現(xiàn)方式帆喇?
第三次嘗試
-?(void)viewDidLoad
{
...
[self?registerCell];??//注冊(cè)cell
}
-?(void)registerCell
{
[_tableView?registerClass:[TextCell?class]?forCellReuseIdentifier:NSStringFromClass([TextCell?class])];
[_tableView?registerClass:[ImageCell?class]?forCellReuseIdentifier:NSStringFromClass([ImageCell?class])];
[_tableView?registerClass:[TextImageCell?class]?forCellReuseIdentifier:NSStringFromClass([TextImageCell?class])];
}
-?(UITableViewCell?*)tableView:(UITableView?*)tableView?cellForRowAtIndexPath:(NSIndexPath?*)indexPath
{
Person?*p?=?_persons[indexPath.row];
BaseCell?*cell;
NSString?*cellIdentifier;
switch?(p.showtype)?{
case?PersonShowText:
cellIdentifier?=?NSStringFromClass([TextCell?class]);
break;
case?PersonShowAvatar:
cellIdentifier?=?NSStringFromClass([ImageCell?class]);
break;
case?PersonShowTextAndAvatar:
cellIdentifier?=?NSStringFromClass([TextImageCell?class]);
break;
default:
cellIdentifier?=?NSStringFromClass([UITableViewCell?class]);
break;
}
cell?=?[tableView?dequeueReusableCellWithIdentifier:cellIdentifier?forIndexPath:indexPath];
[cell?setPerson:p];
return?cell;
}
可以看到警医,這次我們調(diào)用了- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier方法,把tableView和cell先配置好,并且在cellForRowAtIndexPath方法里面预皇,去掉了if (!cell) {...}的處理侈玄,代碼看起來更加簡(jiǎn)潔。
為什么不再需要判斷cell是否為空吟温?因?yàn)橥ㄟ^registerClass方法注冊(cè)了cell之后序仙,dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath方法會(huì)確保有一個(gè)可用的cell返回。
當(dāng)然鲁豪,我們可以把類型判斷的這段代碼提取出來潘悼,讓cellForRowAtIndexPath方法看起來更加簡(jiǎn)潔
@interface?Person?:?NSObject
......
@property?(nonatomic,?strong)?NSString?*cellIdentifier;
@end
@implementation?Person
-?(NSString?*)cellIdentifier
{
if?(_showtype?==?PersonShowTextAndAvatar)?{
return?NSStringFromClass([TextImageCell?class]);
}?else?if?(_showtype?==?PersonShowAvatar){
return?NSStringFromClass([ImageCell?class]);
}?else?{
return?NSStringFromClass([TextCell?class]);
}
}
@end
現(xiàn)在cellForRowAtIndexPath方法看起來就像下面這樣,明顯簡(jiǎn)潔多了
-?(UITableViewCell?*)tableView:(UITableView?*)tableView?cellForRowAtIndexPath:(NSIndexPath?*)indexPath
{
Person?*p?=?_persons[indexPath.row];
BaseCell?*cell;
NSString?*cellIdentifier;
cellIdentifier?=?p.cellIdentifier;
cell?=?[tableView?dequeueReusableCellWithIdentifier:cellIdentifier?forIndexPath:indexPath];
[cell?setPerson:p];
return?cell;
}
結(jié)論:
使用- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier和- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath可以讓UITableView處理多種類型的cell更加靈活和輕松爬橡。