在某些業務場景下,同一個UITableView需要支持多種UITableViewCell。考慮一下即時通信的聊天對話列表,不同的消息類型對應於不同的UITableViewCell,同一個tableview往往需要支持多達10種以上的cell類型,例如文本、圖片、位置、紅包等等。一般情況下,UITableViewCell往往會和業務數據模型綁定,來展示數據。根據不同的業務數據,對應不同的cell。本文將探討如何有效的管理和加載多種類型的cell。
為了方便討論,假設我們要實現一個員工管理系統。一個員工包含名字和頭像。如果員工只有名字,則只展示名字,如果只有頭像,則只展示頭像,如果既有名字,又有頭像,則需要既展示頭像又展示名字。
我們用一個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;
}這段代碼實現了根據不同的業務模型選取和顯示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對象,來動態生成cell,避免了兩次調用switch case的重復代碼。但是,有沒有更好的實現方式?
第三次嘗試
- (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現在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更加靈活和輕松。
訪問示例代碼