iOS 中多种cell情况下的处理

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

 

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



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

源码JHCellConfig,一种十分灵活易变的适用于创建有多种cell的UITableView的方法,不需要使用switch...case,在调整不同种cell的顺序、及增删某种cell时极其方便 -----本类相当于将tableViewcell所需的基本信息存储下来,以便放到数组管理 2015.3.30已更新 使用Nib写cell的童鞋,请到github上下载最新代码,新加了一个支持Nib的方法哟! 关于如何使用请看demo,注释写了很多 优点:改变不同类型cell的顺序、增删时,极为方便,只需改变VC数据源数组即可,无需在多个tableView代理方法逐个修改 缺点:大家帮我找一找,什么样的需求会难以实现,感谢^_^ 我会不定期更新此demo,加上一些“高级”用法,即应对不同需求时的用法,可以关注下面的微信公众号,更新时我会给你们发消息 · 加入了“高级评论”的示例代码,根据数据源数组来显示评论列表,个数不确定、评论内容长度不确定(即cell高度不确定),可在工程搜索 高级评论 查看相关代码。 · 加入了cell上按钮触发事件绑定的示例代码,手势等同理。 · 对不同种cell进行不同设置时,通过 其对应的 cellConfig.title 进行判断。 (这样,不论你将dataArray如何修改,插入、删除、改变顺序,都无须再次修改这里的判断) 此方法的核心思想就是两个字 “化简”。 通常写多种cell时,不同cell有不同设置,需要在多个代理方法进行判断,而且很多是用switch...case来写。这样一旦修改,就需要修改多处。 而通过JHCellConfig类提供的方法进行化简后,要改两种cell的顺序,就只需交换两行对应的代码;要删除或添加某种cell,就只需注释/删除或添加一行代码。 y = x + 3x + 5x 化简-> y = 9x PM 提需求: 把x换成a! y = x + 3x + 5x 需修改3处,y = 9x只需修改1处
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值