在某些業務場景下,同一個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更加靈活和輕松。
訪問示例代碼