优雅地分离tableview回调

你是否遇到过这样的需求,在tableview中显示一列数据,点击某一个cell时,在此cell下显示相应的附加信息。如下图:

你是不是觉得需求很容易实现,只要使用tableview的insertRowsAtIndexPaths:withRowAnimation:插入一个附加cell就可以了(为表述清楚,我们姑且称被点击的cell为原始cell,展示的附加信息为附加cell)。如果你是这么做的,那么后遗症来了:展开的附加cell影响了后面cell的indexPath,当我们对后面的cell配置、点击、增加、删除、系统回调等等操作时,都需要考虑附加cell对indexPath的影响。例如我们已经展开了row == 0的原始cell的附加cell,配置row == 0的附加cell实际是对tableview的row == 1的配置;配置原始数据的row == 1时,实际是配置tableview的row == 2的cell。每次indexPath相关操作都要转换一遍(特别是当展开的附件cell很多时),非常麻烦,且数据源杂乱、代码可读性差。

本文介绍了一种方法消除附加cell对后面的indexPath产生的影响;并且使delegate和dataSourse回调只配置原始cell,将附加cell的配置分离出来。

1.首先定义两种数据类型:

typedef NSIndexPath ActualIndexPath;

实际indexPath:考虑附加cell影响的indexPath,暨系统调用传入的indexPath。

typedef NSIndexPath LogicIndexPath;

逻辑indexPath:不考虑附加cell影响的indexPath,暨我们希望在代码中使用的indexPath。

参照上面的例子解释:展开row == 0的原始cell后,row == 0的原始cell的附加cell的实际indexPath.row为 1,逻辑indexPath.row为0;row == 1的原始cell实际indexPath.row为2,逻辑indexPath.row 为1。

2.为tableview增加了一个assistantDelegate,负责附加cell的配置。

@interfaceYFAssistantTableView :UITableView

@property(nonatomic,weak)id assistantDelegate;

YFAssistantDelegate中声明了配置附加cell的方法(为了减少过多的protocol声明,在不影响代码可读性前提下,将附加cell的delegate和dataSource统一放到assistantDelegate里)。理论上YFAssistantDelegate里面的方法应该是UITableViewDelegate和UITableViewDataSource的并集,但很多方法并不会使用所以不再声明。

@protocol YFAssistantDelegate

//截取了部分YFAssistantDelegate的方法

@required

- (UITableViewCell*)YFAssistantTableView:(YFAssistantTableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath;

- (BOOL)YFAssistantTableView:(YFAssistantTableView*)tableView shouldSpreadAssistantAtIndexPath:(NSIndexPath*)indexPath;- (CGFloat)YFAssistantTableView:(YFAssistantTableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath;  @end

3.将ActualIndexPath转化为LogicIndexPath

- (LogicIndexPath*)actualIndexPath2Logic:(ActualIndexPath*)indexPath{

NSIntegersection = indexPath.section;

__block NSInteger row = indexPath.row;

//self.assistantsIndexPaths存储了所有已展开的附加cell的indexPath

[self.assistantsIndexPaths enumerateObjectsUsingBlock:^(ActualIndexPath *obj,NSUInteger idx,BOOL *stop) {

if(obj.section== section && obj.row<=indexPath.row) {

row--;

}

}];

return[NSIndex PathindexPathForRow:row inSection:section];

}

3.将对附加cell的方法调用从delegate(dataSource)转到assistantDelegate。

这里用Method Swizzling钩住delegate(dataSource)的每一个方法,在hook方法里首先将ActualIndexPath转为LogicIndexPath,然后判断传进来的indexPath指向的cell的类型,如果指向原始cell那么调用delegate(dataSource)方法并传入LogicIndexPath;如果指向附加cell那么调用assistantDelegate方法并传入LogicIndexPath。(以下以tableView:cellForRowAtIndexPath:为例,其他方法一样)

Method method1 = class_getInstanceMethod([delegate class], @selector(tableView:cellForRowAtIndexPath:));

const char *type = method_getTypeEncoding(method1);

IMP imp = class_getMethodImplementation([self class], @selector(hookTableView:cellForRowAtIndexPath:);

class_addMethod([delegate class], @selector(hookTableView:cellForRowAtIndexPath:), imp, type);

Method method2 = class_getInstanceMethod([delegate class], @selector(hookTableView:cellForRowAtIndexPath:));

method_exchangeImplementations(method1, method2);

- (UITableViewCell*)hookTableView:(YFAssistantTableView*)tableView cellForRowAtIndexPath:(ActualIndexPath*)indexPath{

LogicIndexPath * logicIndexPath = 实际indexPath转为逻辑indexPath(indexPath);

if(indexPath指向附加cell) {

return [tableView.assistantDelegate YFAssistantTableView:tableView cellForRowAtIndexPath: logicIndexPath ];

}

//indexPath指向原始cell

return [self hookTableView:tableView cellForRowAtIndexPath: logicIndexPath ];

}

至此,YFAssistantTableView的基本思路和主要方法已经介绍完毕。在实际使用中,我们只需要关心哪个indexPath的附加cell需要展开、收回,而不用关心indexPath的变化;并将原始cell和附加cell的分离解耦,调用配置数据清晰顺畅、代码可读性强。附上demo

源码: https://github.com/haltNoShut/YFAssistantTableView

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章