在項目開發(fā)中UITableView和UICollectionView應(yīng)該是最長用的控件了吧,而這兩種控件的核心是cell的處理和展示。隨著App的發(fā)展和需求的不斷累加,頁面是單一cell的情況越來越少,更多的是各種復(fù)雜cell的組合。常見的比如App的首頁

那么像這種頁面我們是如何處理cell的呢?
1.最常見的也是很多人會不經(jīng)思考的,直接根據(jù)indexPath一一對應(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ù)上改一個地方tableview的delegate和datasource的方法都需要改,成本很高。而且cellForRowAtIndexPath的方法里面是清一色的if-else,然后是做了各種各樣的事情,很容易造成代碼的臃腫,動不動就是幾十行或者幾百行代碼,不利于閱讀和重用。
這種方案的缺點(diǎn)有以下幾點(diǎn):
1.一般情況下項目中不建議出現(xiàn)0、1等具體的數(shù)字,因為它除了表示位置之外,毫無其他意義。
2.容易出錯,在cell代理方法,高度代理方法,點(diǎn)擊代理方法里面要保持一致,容易出錯。
3.不方便修改,如果要修改兩個cell的順序或者添加修改,要修改好幾個地方,改動太大。
2.根據(jù)model來對應(yīng)cell,cell面向model開發(fā)
前面提到了不因該出現(xiàn)indexPath等具體的位置數(shù)字,對于一個tableview,位置數(shù)字肯定是有的,我們要消除數(shù)字,那就得找到相應(yīng)的數(shù)據(jù)來代替它。這里,主要的場景一般都是一個類型的數(shù)據(jù)(model)對應(yīng)一種類型的cell,所以類型是固定的,所以我們用一個枚舉來定義所有類型的cell
typedefNS_ENUM(NSInteger, HomeCellType) {
?HomeCellTypeOne =0,
?HomeCellTypeTwo?
?HomeCellTypeThree,?
?HomeCellTypeFourl,?
?};
然后在cellForRow方法,根據(jù)model類型加載對應(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ù)用、刪除、添加、改動順序,只需要改動數(shù)據(jù)源即可,其他不需要動,改動量很小。但是這樣寫的還不是很好,cell和datasource的cellForRowAtIndexPath耦合的還有點(diǎn)嚴(yán)重。那如果其他的地方只是用到了部分cell類型,我們還需要把上面的代碼再copy一份?或者說我想讓cell根據(jù)model去自動選擇cell類型,而不是import各種cell。頭文件,在cellForRowAtIndexPath方法里面判斷,不依賴具體的cell呢?
那么我的面向協(xié)議開發(fā)的設(shè)計模式就上場了。就是讓model繼承一個協(xié)議,該協(xié)議實現(xiàn)了cell的一些方法,例如cell的復(fù)用標(biāo)示、cell的類型、cell的高度、cell的點(diǎn)擊事件等。
改進(jìn)版
1.定義協(xié)議接口
@protocol ModelConfigProtocol
@optional
/**
獲取 cell 的復(fù)用標(biāo)識
@return 復(fù)用標(biāo)識
*/
- (nullableNSString*)cellReuseIdentifier;
/**
獲取 cell 的類型
@return cell 的類型
*/
- (cellType)cellType;
/**
獲取 cell 的高度
@param indexPath indexPath
@return 高度
*/
- (CGFloat)cellHeightWithindexPath:(NSIndexPath*)indexPath;
/**
cell 點(diǎn)擊
@param indexPath indexPath
@param other 其它對象
*/
- (void)cellDidSelectRowAtIndexPath:(NSIndexPath*)indexPath other:(_Nullable id)other;
2.然后實現(xiàn)實現(xiàn)該協(xié)議接口。定義一個抽象類的model遵守該協(xié)議實現(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具體實現(xiàn)ModelConfigProtocol的協(xié)議方法
4.定義的一個抽象類的cell,開放賦值的接口
@interfaceBaseCell : UITableViewCell
@property (nonatomic,strong) id<ModelConfigProtocol> model;
@end
@implementation BaseCell
- (void)setModel:(id)model{
}
@end
5.具體的cell繼承自BaseCell,然后子類cell具體實現(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對應(yīng)一種model,如果你想一種model對應(yīng)多種cell,例如微信消息,有文本消息、圖片消息、語音消息、紅包消息、視頻消息等。你可以在具體model的cellType再做一層判斷。最厲害的地方在于可以和MVVM、適配器無縫對接,寫一個BaseViewController實現(xiàn)這些,然后其他ViewController繼承它,只需改變數(shù)據(jù)源,即可實現(xiàn)用最少的代碼實現(xiàn)復(fù)雜的頁面展示。