上篇:谈谈iOS开发如何写个人中心这类页面--静态tableView页面的编写(上)
中篇:谈谈iOS开发如何写个人中心这类页面--静态tableView页面的编写(中)
(6) 为该页面的 TableView 加载所需的 DescribeData 数据:
- (void)loadCellDescribeDatas {
MCDemoTableDescribeData *cell1Data = [[MCDemoTableDescribeData alloc] init];
cell1Data.cellClass = [MCDemoCell1 class];
cell1Data.headerIconName = @"header_icon";
cell1Data.content = @"cell1's content";
cell1Data.selectCellBlock = ^(MCTableBaseCell *cell, MCTableBaseDescribeData *describeData) {
NSLog(@"cell1 selected");
};
MCDemoTableDescribeData *cell2Data = [[MCDemoTableDescribeData alloc] init];
cell2Data.cellClass = [MCDemoCell2 class];
cell2Data.title = @"cell2";
cell2Data.content = @"cell2's content";
cell2Data.switchStatus = YES;
cell2Data.cell2Delegate = self;
MCDemoTableDescribeData *cell3Data = [[MCDemoTableDescribeData alloc] init];
cell3Data.cellClass = [MCDemoCell3 class];
cell3Data.indicateImageName = @"indicate";
cell3Data.title = @"cell3";
cell3Data.subTitle = @"cell3's subtitle";
_cellDescriptionDatas = @[@[cell1Data, cell2Data], @[cell3Data]];
}
在创建了某个 Cell 的 DescribeData 后,我们需要为 DescribeData 设置 cellClass(必须设置项) 以及该 Cell 显示所需的其他数据。有两个地方需要注意:
(7) 添加 UITableViewDataSource 的方法:
#pragma mark - UITableViewDataSource.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return _cellDescriptionDatas.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _cellDescriptionDatas[section].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MCDemoTableDescribeData *data = _cellDescriptionDatas[indexPath.section][indexPath.row];
UITableViewCell *cell = [_tableView dequeueReusableCellWithClassType:data.cellClass];
data.customCellBlock((MCTableBaseCell *)cell, data);
return cell;
}
(8) 添加 UITableViewDelegate 的方法:
#pragma mark - UITableViewDelegate.
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 20;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
MCDemoTableDescribeData *data = _cellDescriptionDatas[indexPath.section][indexPath.row];
UITableViewCell *cell = [_tableView dequeueReusableCellWithClassType:data.cellClass];
data.customCellBlock((MCTableBaseCell *)cell, data);
return [data cellHeight];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
MCDemoTableDescribeData *data = _cellDescriptionDatas[indexPath.section][indexPath.row];
if (data.selectCellBlock) {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
data.selectCellBlock((MCTableBaseCell *)cell, data);
}
}
本方案的详细代码见 demo 的 ViewController。
通过上面步骤我们可以发现本方案的一些优点:
Cell 在页面中的位置由 cellDescribeData 在 cellDescriptionDatas 数组中的位置决定,我们可以通过 loadCellDescribeDatas 方法中调整 cellDescribeData 的顺序来方便的调整 Cell 在页面的位置。
numberOfSectionsInTableView: 和 tableView:numberOfRowsInSection: 可以通过 cellDescriptionDatas 方便获取,无需 if else 语句。
tableView:cellForRowAtIndexPath: 、 tableView:heightForRowAtIndexPath: 以及 tableView:didSelectRowAtIndexPath: 方法可以通过 indexPath 获取 cellDescriptionDatas 中的 DescribeData。DescribeData 中就有了足够的条件能够获取到所需的信息,非常方便。
Cell 的高度支持动态计算。只要自定义的 Cell 重写 sizeThatFits: 方法, 并在中动态计算的高度然后返回即可。
该页面的 Cell 可以方便的移植到其他页面,在有这种需求时,建议使用 MCDemoCell3 那样为 Cell 定义一个 ViewModel 属性,ViewModel 包含且仅包含与该 Cell 展示有关的数据。然后在 setDescribeData: 中根据 describeData 创建出 ViewModel ,代码示例如下:
#pragma mark - Getter & Setter.
- (void)setDescribeData:(MCTableBaseDescribeData *)describeData {
if ([describeData isKindOfClass:MCDemoTableDescribeData.class]) {
MCDemoCell3ViewModel *viewModel = [[MCDemoCell3ViewModel alloc] initWithDescribeData:(MCDemoTableDescribeData *)describeData];
self.viewModel = viewModel;
}
}
- (void)setViewModel:(MCDemoCell3ViewModel *)viewModel {
if (_viewModel == viewModel) {
return;
}
_viewModel = viewModel;
_titleLabel.text = _viewModel.title;
_subTitleLabel.text = _viewModel.subTitle;
_indicateImageView.image = _viewModel.indicateImage;
}
这样可以保证 Cell 拥有更强的复用性。例如,如果需要用在另一个这一类的页面,只需要在 setDescribeData: 中添加几行代码即可:
- (void)setDescribeData:(MCTableBaseDescribeData *)describeData {
if ([describeData isKindOfClass:MCDemoTableDescribeData.class]) {
MCDemoCell3ViewModel *viewModel = [[MCDemoCell3ViewModel alloc] initWithDescribeData:(MCDemoTableDescribeData *)describeData];
self.viewModel = viewModel;
} else if ([describeData isKindOfClass:MCDemoTableDescribeData2.class]) {
MCDemoCell3ViewModel *viewModel = [[MCDemoCell3ViewModel alloc] initWithDescribeData:(MCDemoTableDescribeData2 *)describeData];
self.viewModel = viewModel;
}
}
本方案中 Cell 支持复用,且提供了方便的注册 Cell 类型和获取某类型 Cell 的 category: UITableView+MCRegisterCellClass。
MCTableBaseDescribeData 提供默认的 customCellBlock,一般均可满足需求,这样将定义各个 Cell 样式的代码从 Controller 转移到了各个 Cell 内部,使得 Controller 中代码简洁。
每种方案都有缺点,本方案也不例外,缺点主要表现在两个方面:
由于自定义的 DescribeData 类型中包含了页面所有 Cell 所需的数据,但每一个 Cell 的 DescribeData 用不到这么多属性,所以这样就浪费了一些内存资源。
每一种架构的学习都有一定成本,本方案也需要一些学习成本。
我们解决一个问题通常都会有多种方法,每种方法都各有优缺点。 在解决架构的问题时,我们应该多思考,根据需求切实分析各种方案的优缺点,最后再做出取舍,不要限制自己解决某一问题时一定要用某种方案。
上面应该已经清晰的讲述了本文要解决的问题,并给出了一种解决问题的方案,该方案对我们通常写出的代码进行了一定的抽象,可以保证灵活可扩展以及代码规范一致,并且容易掌握, 经过实践,使用本文的方案进行这类页面的编写的速度也很不错。但正如上面所说,每种方案都有优缺点(本方案优缺点上面已列出),还是需要架构师在进行选型时充分权衡。
当然也欢迎各位提出问题、进行交流。
本文来自网易实践者社区,经作者白天宇授权发布。