iOS 中多种cell情况下的处理

在项目开发中UITableView和UICollectionView应该是最长用的控件了吧,而这两种控件的核心是cell的处理和展示。随着App的发展和需求的不断累加,页面是单一cell的情况越来越少,更多的是各种复杂cell的组合。常见的比如App的首页

 

iOS 中多种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{

    [email protected]"";

}

- (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继承它,只需改变数据源,即可实现用最少的代码实现复杂的页面展示。



作者:落叶随风_90e5
链接:https://www.jianshu.com/p/1d027d45565d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。