在项目开发中UITableView和UICollectionView应该是最长用的控件了吧,而这两种控件的核心是cell的处理和展示。随着App的发展和需求的不断累加,页面是单一cell的情况越来越少,更多的是各种复杂cell的组合。常见的比如App的首页
那么像这种页面我们是如何处理cell的呢?
1.最常见的也是很多人会不经思考的,直接根据indexPath一一对应,写出下面的代码:
- (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) {
}
}
虽然这种在开发阶段很容易,但是在后期的二次开发和维护上改一个地方tableview的delegate和datasource的方法都需要改,成本很高。而且cellForRowAtIndexPath的方法里面是清一色的if-else,然后是做了各种各样的事情,很容易造成代码的臃肿,动不动就是几十行或者几百行代码,不利于阅读和重用。
这种方案的缺点有以下几点:
1.一般情况下项目中不建议出现0、1等具体的数字,因为它除了表示位置之外,毫无其他意义。
2.容易出错,在cell代理方法,高度代理方法,点击代理方法里面要保持一致,容易出错。
3.不方便修改,如果要修改两个cell的顺序或者添加修改,要修改好几个地方,改动太大。
2.根据model来对应cell,cell面向model开发
前面提到了不因该出现indexPath等具体的位置数字,对于一个tableview,位置数字肯定是有的,我们要消除数字,那就得找到相应的数据来代替它。这里,主要的场景一般都是一个类型的数据(model)对应一种类型的cell,所以类型是固定的,所以我们用一个枚举来定义所有类型的cell
typedefNS_ENUM(NSInteger, HomeCellType) {
HomeCellTypeOne =0,
HomeCellTypeTwo
HomeCellTypeThree,
HomeCellTypeFourl,
};
然后在cellForRow方法,根据model类型加载对应的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就知道如何去处理相应的cell了,清晰易理解。而且如果想复用、删除、添加、改动顺序,只需要改动数据源即可,其他不需要动,改动量很小。但是这样写的还不是很好,cell和datasource的cellForRowAtIndexPath耦合的还有点严重。那如果其他的地方只是用到了部分cell类型,我们还需要把上面的代码再copy一份?或者说我想让cell根据model去自动选择cell类型,而不是import各种cell。头文件,在cellForRowAtIndexPath方法里面判断,不依赖具体的cell呢?
那么我的面向协议开发的设计模式就上场了。就是让model继承一个协议,该协议实现了cell的一些方法,例如cell的复用标示、cell的类型、cell的高度、cell的点击事件等。
改进版
1.定义协议接口
@protocol ModelConfigProtocol
@optional
/**
获取 cell 的复用标识
@return 复用标识
*/
- (nullableNSString*)cellReuseIdentifier;
/**
获取 cell 的类型
@return cell 的类型
*/
- (cellType)cellType;
/**
获取 cell 的高度
@param indexPath indexPath
@return 高度
*/
- (CGFloat)cellHeightWithindexPath:(NSIndexPath*)indexPath;
/**
cell 点击
@param indexPath indexPath
@param other 其它对象
*/
- (void)cellDidSelectRowAtIndexPath:(NSIndexPath*)indexPath other:(_Nullable id)other;
2.然后实现实现该协议接口。定义一个抽象类的model遵守该协议实现协议
@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具体实现ModelConfigProtocol的协议方法
4.定义的一个抽象类的cell,开放赋值的接口
@interfaceBaseCell : UITableViewCell
@property (nonatomic,strong) id<ModelConfigProtocol> model;
@end
@implementation BaseCell
- (void)setModel:(id)model{
}
@end
5.具体的cell继承自BaseCell,然后子类cell具体实现setModel方法
6.TableView代理里数据返回
#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对应一种model,如果你想一种model对应多种cell,例如微信消息,有文本消息、图片消息、语音消息、红包消息、视频消息等。你可以在具体model的cellType再做一层判断。最厉害的地方在于可以和MVVM、适配器无缝对接,写一个BaseViewController实现这些,然后其他ViewController继承它,只需改变数据源,即可实现用最少的代码实现复杂的页面展示。