含有 tableView 的 ViewController 的瘦
前言
最近在开发 iOS 时,发现有很多的业务展示界面都是使用了 tableView,而且大部分展示的界面都是没有很复杂的业务交互和界面结构。例如下面的2个界面
列表一 列表二
一般来说,正常的开发这上面的2个界面,会写出一下的代码
@interface ViewController1: UIViewController <UITableViewDataSource, UITableViewDelegate>
/* 省略内容*/
@end
@implementation ViewController1
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.manager getDataSourceCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ViewController1CellConfig *config = [self.manager configInfoFromIndexPath: indexPath];
ViewController1Cell * cell = [tableView dequeueReusableCellWithIdentifier:@"ViewController1Cell" forIndexPath:indexPath ];
[cell configCell: ViewController1CellConfig];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ViewControllerCellHeight;
}
@end
@interface ViewController2: UIViewController <UITableViewDataSource, UITableViewDelegate>
/* 省略内容*/
@end
@implementation ViewController2
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.manager getDataSourceCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ViewController2CellConfig *config = [self.manager configInfoFromIndexPath: indexPath];
ViewController2Cell * cell = [tableView dequeueReusableCellWithIdentifier:@"ViewController2Cell" forIndexPath:indexPath ];
[cell configCell: ViewController2CellConfig];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ViewControllerCellHeight;
}
@end
一对比,会发现这2个 ViewController 中的代码非常类似,而在我们的业务项目中,远远不止2个类似的代码。既然代码都是如此的类似,那么,我们是否可以将这部分代码抽离出来,抽象出一个公共的,复用的类呢。答案是肯定的。
观察上面代码可以发现, 上述代码,其实都是在重复一个流程, ViewController 从 manager 中获取到数据, 然后在将数据传递到 tableView 和 tableViewCell。 因此,只要我们确定了数据传输的接口,就可以将数据传输这部分代码抽象出来。
定义 Manager 到 ViewController 的接口
从上面代码可知, 从 manager 中获取数据的接口有 getDataSourceCount
和 configInfoFromIndexPath
所以,我们可以使用 Protocol 的特性, 定义一个接口文件EPCommonManagerProviderDelegagte
,如下:
@protocol EPCommonManagerProviderDelegagte <NSObject>
- (id)cellConfigurationWithIndexPath:(NSIndexPath *)indexPath;
- (NSUInteger)numberOfRowsInSection:(NSInteger)section;
@end
这个协议一共定义了2个内容,负责提供 tableView 行数的 numberOfRowsInSection
和 负责提供 tableViewCell 配置数据的 cellConfigurationWithIndexPath
定义 ViewController 到 tableView 和 Cell 的接口
由于 ViewController 到 tableView 的接口就是 dataSource 的协议内容,这里不多讨论。 下面主要定义了 ViewController 到 Cell 的接口EPCommonCellConfigDelegate
@protocol EPCommonCellConfigDelegate <NSObject>
- (void)cellConfig:(id)cellConfiguration;
@end
抽离出 ViewController 中 tableView 的 dataSource
@interface EPCommonTableViewProvider : NSObject <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, strong) id<EPCommonCellManagerProviderDelegagte> manager;
@property (nonatomic, strong) NSString *cellIdentifier;
@property (nonatomic, assign) CGFloat cellHeight;
@end
@implementation EPCommonTableViewProvider
- (instancetype)init {
if (self = [super init]) {
_cellHeight = 44;
}
return self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.manager && [self.manager respondsToSelector:@selector(numberOfRowsInSection:)]) {
return [self.manager numberOfRowsInSection:section];
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.cellIdentifier.length == 0) {
return [[UITableViewCell alloc] init];
}
id<EPCommonCellConfigDelegate> cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
if (self.manager && [self.manager respondsToSelector:@selector(cellConfigurationWithIndexPath:)]) {
id cellConfiguration = [self.manager cellConfigurationWithIndexPath:indexPath];
if (cell && [cell respondsToSelector:@selector(cellConfig:)]) {
[cell cellConfig:cellConfiguration];
}
}
return (UITableViewCell *)cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.cellHeight;
}
@end
实际使用
- manager 实现
EPCommonManagerProviderDelegagte
协议
@interface ViewController1Manager : NSObject <EPCommonManagerProviderDelegagte>
@end
@implementation ViewController1Manager
- ( *)cellConfigurationWithIndexPath:(NSIndexPath *)indexPath {
return [[ViewControllerCell1Config alloc] initWithModel:self.dataSource[indexPath.row] sortType:self.sortType];
}
- (NSUInteger)numberOfRowsInSection:(NSInteger)section {
return self.dataSource.count;
}
@end
- cell 实现 EPCommonCellConfigDelegate 协议
@interface ViewController1Cell : UITableViewCell <EPCommonCellConfigDelegate>
@end
@implementation ViewController1Cell
- (void)cellConfig:(ViewControllerCell1Config *)config {
/* 配置 cell */
}
@end
- 将 EPCommonTableViewProvider 配置 给 tableView 的 dataSource
@interface ViewController1: UIViewController
/* 省略内容*/
@property (nonatomic, strong) EPCommonTableViewProvider *provider;
@property (nonatomic, strong) ViewController1Manager *manager;
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation ViewController1
- (EPCommonTableViewProvider *)provider {
if (_provider == nil) {
_provider = [[EPCommonTableViewProvider alloc] init];
_provider.manager = self.manager;
_provider.cellHeight = 50;
_provider.cellIdentifier = @"ViewController1Cell";
}
return _provider;
}
- (UITableView *)tableView {
if (_tableView == nil) {
_tableView = [[UITableView alloc] init];
_tableView.dataSource = self.provider;
[_tableView registerNib:[UINib nibWithNibName:@"ViewController1Cell" bundle:nil] forCellReuseIdentifier:@"ViewController1Cell"];
}
return _tableView;
}
@end
对于简单的展示页面,manager 和 cell 的代码几乎没有变化,但是抽离出一个 EPCommonTableViewProvider
可以有效的减少 ViewController 中代码行数,并且也可以减少重复代码的数量。