谈谈iOS开发如何写个人中心这类页面--静态tableView页面的编写(下)

西西吹雪2018-05-25 13:12

上篇:谈谈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 显示所需的其他数据。有两个地方需要注意:

  • selectCellBlock: 是选中该 cell 时执行的动作,在之后的 tableView:didSelectRowAtIndexPath: 时会用到;
  • customCellBlock: 这个 block 默认实现是将新建的 cell1Data/Cell2Data/Cell3Data 赋值给 Cell 的 describeData 属性, 这样在新建 Cell 时调用 customCellBlock 就可以按照 Cell 中重写的 setDescribeData: 方法中的代码为 Cell 填充数据以及改变样式。 如果需要自定义 customCellBlock,请在自定义的 customCellBlock 中先调用 MCTableBaseDescribeData 的 defaultCustomCellBlock。

(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。

方案优点分析

通过上面步骤我们可以发现本方案的一些优点:

  1. Cell 在页面中的位置由 cellDescribeData 在 cellDescriptionDatas 数组中的位置决定,我们可以通过 loadCellDescribeDatas 方法中调整 cellDescribeData 的顺序来方便的调整 Cell 在页面的位置。

  2. numberOfSectionsInTableView: 和 tableView:numberOfRowsInSection: 可以通过 cellDescriptionDatas 方便获取,无需 if else 语句。

  3. tableView:cellForRowAtIndexPath: 、 tableView:heightForRowAtIndexPath: 以及 tableView:didSelectRowAtIndexPath: 方法可以通过 indexPath 获取 cellDescriptionDatas 中的 DescribeData。DescribeData 中就有了足够的条件能够获取到所需的信息,非常方便。

  4. Cell 的高度支持动态计算。只要自定义的 Cell 重写 sizeThatFits: 方法, 并在中动态计算的高度然后返回即可。

  5. 该页面的 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;
        }
    }
    
  6. 本方案中 Cell 支持复用,且提供了方便的注册 Cell 类型和获取某类型 Cell 的 category: UITableView+MCRegisterCellClass。

  7. MCTableBaseDescribeData 提供默认的 customCellBlock,一般均可满足需求,这样将定义各个 Cell 样式的代码从 Controller 转移到了各个 Cell 内部,使得 Controller 中代码简洁。

方案缺点分析

每种方案都有缺点,本方案也不例外,缺点主要表现在两个方面:

  1. 由于自定义的 DescribeData 类型中包含了页面所有 Cell 所需的数据,但每一个 Cell 的 DescribeData 用不到这么多属性,所以这样就浪费了一些内存资源。

  2. 每一种架构的学习都有一定成本,本方案也需要一些学习成本。

四、总结

我们解决一个问题通常都会有多种方法,每种方法都各有优缺点。 在解决架构的问题时,我们应该多思考,根据需求切实分析各种方案的优缺点,最后再做出取舍,不要限制自己解决某一问题时一定要用某种方案。

上面应该已经清晰的讲述了本文要解决的问题,并给出了一种解决问题的方案,该方案对我们通常写出的代码进行了一定的抽象,可以保证灵活可扩展以及代码规范一致,并且容易掌握, 经过实践,使用本文的方案进行这类页面的编写的速度也很不错。但正如上面所说,每种方案都有优缺点(本方案优缺点上面已列出),还是需要架构师在进行选型时充分权衡。

当然也欢迎各位提出问题、进行交流。


 本文来自网易实践者社区,经作者白天宇授权发布。