在項(xiàng)目開發(fā)中UITableView和UICollectionView應(yīng)該是最長用的控件了吧巴席,而這兩種控件的核心是cell的處理和展示。隨著App的發(fā)展和需求的不斷累加祟牲,頁面是單一cell的情況越來越少狂魔,更多的是各種復(fù)雜cell的組合椎咧。常見的比如App的首頁
那么像這種頁面我們是如何處理cell的呢侠讯?
1.最常見的也是很多人會(huì)不經(jīng)思考的挖藏,直接根據(jù)indexPath一一對(duì)應(yīng),寫出下面的代碼:
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
? ? if(indexPath.section==0) {
? ? }
}
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{
? ? [tableViewdeselectRowAtIndexPath:indexPath animated:YES];
? ? if(indexPath.section==0) {
? ?}
}
雖然這種在開發(fā)階段很容易厢漩,但是在后期的二次開發(fā)和維護(hù)上改一個(gè)地方tableview的delegate和datasource的方法都需要改膜眠,成本很高。而且cellForRowAtIndexPath的方法里面是清一色的if-else溜嗜,然后是做了各種各樣的事情宵膨,很容易造成代碼的臃腫,動(dòng)不動(dòng)就是幾十行或者幾百行代碼炸宵,不利于閱讀和重用辟躏。
這種方案的缺點(diǎn)有以下幾點(diǎn):
1.一般情況下項(xiàng)目中不建議出現(xiàn)0、1等具體的數(shù)字土全,因?yàn)樗吮硎疚恢弥馍铀觯翢o其他意義。
2.容易出錯(cuò)裹匙,在cell代理方法瑞凑,高度代理方法,點(diǎn)擊代理方法里面要保持一致概页,容易出錯(cuò)籽御。
3.不方便修改,如果要修改兩個(gè)cell的順序或者添加修改惰匙,要修改好幾個(gè)地方技掏,改動(dòng)太大。
2.根據(jù)model來對(duì)應(yīng)cell项鬼,cell面向model開發(fā)
前面提到了不因該出現(xiàn)indexPath等具體的位置數(shù)字零截,對(duì)于一個(gè)tableview,位置數(shù)字肯定是有的秃臣,我們要消除數(shù)字涧衙,那就得找到相應(yīng)的數(shù)據(jù)來代替它。這里奥此,主要的場景一般都是一個(gè)類型的數(shù)據(jù)(model)對(duì)應(yīng)一種類型的cell弧哎,所以類型是固定的,所以我們用一個(gè)枚舉來定義所有類型的cell
typedefNS_ENUM(NSInteger, HomeCellType) {
?HomeCellTypeOne =0,
?HomeCellTypeTwo?
?HomeCellTypeThree,?
?HomeCellTypeFourl,?
?};
然后在cellForRow方法稚虎,根據(jù)model類型加載對(duì)應(yīng)的cell撤嫩,例如:
? ?id model = self.viewModel.dataArray[indexPath.row];
switch([self?getHomeCellType] ){
? caseHomeCellTypeOne:
?HomeCellOne?*cell = [collectionView dequeueReusableCellWithReuseIdentifier:[HomeCellOne cellIdentifier] forIndexPath:indexPath];
? ?breke;
caseHomeCellTypeOne:
?HomeCellTwo *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[HomeCellTwo?cellIdentifier] forIndexPath:indexPath];
? ?breke;
? ?....
}
?- (HomeCellType)getHomeCellType:(id)model {
? ? ? ? HomeCellType type = HomeCellTypeOne;
? ? ? ? if([model isKindOfClass:[HomeCellTypeOneModel class]]) {
? ? ? ? ? ? type = HomeCellTypeOne;
? ? ? ? }else if([model isKindOfClass:[HomeCellTypeTwoModel class]]) {
? ? ? ? ? ? type = HomeCellTypeTwo;
? ? ? ? }else if(){
? ? ? ? }
? ? ? ...
}
這樣看到了cellType或者model就知道如何去處理相應(yīng)的cell了,清晰易理解蠢终。而且如果想復(fù)用序攘、刪除茴她、添加、改動(dòng)順序程奠,只需要改動(dòng)數(shù)據(jù)源即可丈牢,其他不需要?jiǎng)樱膭?dòng)量很小瞄沙。但是這樣寫的還不是很好己沛,cell和datasource的cellForRowAtIndexPath耦合的還有點(diǎn)嚴(yán)重。那如果其他的地方只是用到了部分cell類型距境,我們還需要把上面的代碼再copy一份申尼?或者說我想讓cell根據(jù)model去自動(dòng)選擇cell類型,而不是import各種cell垫桂。頭文件师幕,在cellForRowAtIndexPath方法里面判斷,不依賴具體的cell呢诬滩?
那么我的面向協(xié)議開發(fā)的設(shè)計(jì)模式就上場了们衙。就是讓model繼承一個(gè)協(xié)議,該協(xié)議實(shí)現(xiàn)了cell的一些方法碱呼,例如cell的復(fù)用標(biāo)示蒙挑、cell的類型、cell的高度愚臀、cell的點(diǎn)擊事件等忆蚀。
改進(jìn)版
1.定義協(xié)議接口
@protocol ModelConfigProtocol
@optional
/**
獲取 cell 的復(fù)用標(biāo)識(shí)
@return 復(fù)用標(biāo)識(shí)
*/
- (nullableNSString*)cellReuseIdentifier;
/**
獲取 cell 的類型
@return cell 的類型
*/
- (cellType)cellType;
/**
獲取 cell 的高度
@param indexPath indexPath
@return 高度
*/
- (CGFloat)cellHeightWithindexPath:(NSIndexPath*)indexPath;
/**
cell 點(diǎn)擊
@param indexPath indexPath
@param other 其它對(duì)象
*/
- (void)cellDidSelectRowAtIndexPath:(NSIndexPath*)indexPath other:(_Nullable id)other;
2.然后實(shí)現(xiàn)實(shí)現(xiàn)該協(xié)議接口。定義一個(gè)抽象類的model遵守該協(xié)議實(shí)現(xiàn)協(xié)議
@interface BaseModel : NSObject<ModelConfigProtocol>
@end
@implementation BaseModel
- (cellType)cellType{
? ? return0;
}
- (NSString*)cellReuseIdentifier{
? ? return@"";
}
- (CGFloat)cellHeightWithindexPath:(NSIndexPath*)indexPath{
? ? return0.0;
}
- (void)cellDidSelectRowAtIndexPath:(NSIndexPath*)indexPath other:(_Nullable id)other{
? ? return;
}
@end
3.具體的model繼承自BaseModel姑裂,然后子類model具體實(shí)現(xiàn)ModelConfigProtocol的協(xié)議方法
4.定義的一個(gè)抽象類的cell馋袜,開放賦值的接口
@interfaceBaseCell : UITableViewCell
@property (nonatomic,strong) id<ModelConfigProtocol> model;
@end
@implementation BaseCell
- (void)setModel:(id)model{
}
@end
5.具體的cell繼承自BaseCell,然后子類cell具體實(shí)現(xiàn)setModel方法
6.TableView代理里數(shù)據(jù)返回
#pragma mark ---- UITableViewDelegate ----
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
id<ModelConfigProtocol>?model =self.listArray[indexPath.row];
return [model cellHeightWithindexPath:indexPath];
}
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
id<ModelConfigProtocol> mdoel =self.viewModel.dataArray[indexPath.row];
[model cellDidSelectRowAtIndexPath:indexPath other:nil];
}
#pragma mark ---- UITableViewDataSource ----
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView;
{
return1;
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
return self.viewModel.dataArray.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath;
{
id<ModelConfigProtocol>?model =self.viewModel.dataArray[indexPath.row];
? ? BaseCell *cell = [tableView dequeueReusableCellWithIdentifier:[model cellReuseIdentifier]];
? ? cell.cellConfig = model;
returncell;
}
一般的一種類型的cell對(duì)應(yīng)一種model舶斧,如果你想一種model對(duì)應(yīng)多種cell欣鳖,例如微信消息,有文本消息茴厉、圖片消息泽台、語音消息、紅包消息矾缓、視頻消息等怀酷。你可以在具體model的cellType再做一層判斷。最厲害的地方在于可以和MVVM嗜闻、適配器無縫對(duì)接蜕依,寫一個(gè)BaseViewController實(shí)現(xiàn)這些,然后其他ViewController繼承它,只需改變數(shù)據(jù)源样眠,即可實(shí)現(xiàn)用最少的代碼實(shí)現(xiàn)復(fù)雜的頁面展示友瘤。