在某些業(yè)務場景下,同一個UITableView需要支持多種UITableViewCell??紤]一下即時通信的聊天對話列表,不同的消息類型對應于不同的UITableViewCell,同一個tableview往往需要支持多達10種以上的cell類型,例如文本、圖片、位置、紅包等等。一般情況下,UITableViewCell往往會和業(yè)務數(shù)據(jù)模型綁定,來展示數(shù)據(jù)。根據(jù)不同的業(yè)務數(shù)據(jù),對應不同的cell。本文將探討如何有效的管理和加載多種類型的cell。
為了方便討論,假設我們要實現(xiàn)一個員工管理系統(tǒng)。一個員工包含名字和頭像。如果員工只有名字,則只展示名字,如果只有頭像,則只展示頭像,如果既有名字,又有頭像,則需要既展示頭像又展示名字。
我們用一個Person類表示員工,用三種不同的cell來處理不同的展示情況。
@interface?Person?:?NSObject
@property?(nonatomic,?copy)?NSString?*name;
@property?(nonatomic,?strong)?NSString?*avatar;
@end
/*負責展示只有名字的員工*/
@interface?TextCell?:?BaseCell
-?(void)setPerson:(Person?*)p;
@end
/*負責展示只有頭像的員工*/
@interface?ImageCell?:?BaseCell
-?(void)setPerson:(Person?*)p;
@end
/*負責展示只有既有名字又有頭像的員工*/
@interface?TextImageCell?:?BaseCell
-?(void)setPerson:(Person?*)p;
@end
這三個類都繼承了BaseCell,BaseCell繼承UITableViewCell
@interface?BaseCell?:?UITableViewCell
-?(void)setPerson:(Person?*)p;
@end
下面我們在UITableView的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;
}
這段代碼實現(xiàn)了根據(jù)不同的業(yè)務模型選取和顯示Cell的邏輯。但是這段代碼包含了重復代碼,switch case被調用了兩次。我們改進一下代碼:
第二次嘗試
-?(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;
}
這次比第一次的代碼看起來好了不少,通過一個通用的Class對象,來動態(tài)生成cell,避免了兩次調用switch case的重復代碼。但是,有沒有更好的實現(xiàn)方式?
第三次嘗試
-?(void)viewDidLoad
{
...
[self?registerCell];??//注冊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;
}
可以看到,這次我們調用了- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier方法,把tableView和cell先配置好,并且在cellForRowAtIndexPath方法里面,去掉了if (!cell) {...}的處理,代碼看起來更加簡潔。
為什么不再需要判斷cell是否為空?因為通過registerClass方法注冊了cell之后,dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath方法會確保有一個可用的cell返回。
當然,我們可以把類型判斷的這段代碼提取出來,讓cellForRowAtIndexPath方法看起來更加簡潔
@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方法看起來就像下面這樣,明顯簡潔多了
-?(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;
}
結論:
使用- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier和- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath可以讓UITableView處理多種類型的cell更加靈活和輕松。