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

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

一、本文讲的是什么问题?

在开发 iOS 应用时,基本都会遇到个人中心、设置、详情信息等页面,这里截取了某应用的详情编辑页面和个人中心页面,如下:


我们以页面结构的角度考虑这两个页面的共同点:

  • 每个页面都有多种样式的 View。
  • 两个页面的 View 数量都不算特别多,并不像常见的产品列表页面那样可以多次加载更多数据。
  • 每个 View 都可能根据视觉或交互设计师要求随时变动位置,比如上图中编辑图片页面调整一下"添加描述"的位置。
  • 页面有些 View 的数据展示不是网络数据,比如个人中心下面几行每一行左边的 title 都是固定的。

如果觉着以一个应用为例子不具有说服力,那么大家可以打开各自手机上的应用看一下(比如微信、淘宝等)。 根据我个人的项目开发经验,基本上每个应用都会有拥有一些具有上述共同点(一个或多个)的页面。

那么这类页面应该怎么编写才能保证规范性以及灵活性?本文就是讲述了这一类页面应该如何编写。

提示:阅读本文时,建议参考 本文的demo,本文的demo提供了 Objective-C 版本和 Swift 版本。

二、常见的几类写法

使用 Xib

使用 Xib 对这类页面进行可视化编辑是一种不错的选择,但这类方案也有部分缺点,比如:

  • 编写布局可能需要直接使用 Autolayout,这样一旦出了问题可能不如使用 Masonry 写约束调试方便;
  • Xib 中各视图的颜色值或 frame 值如果需要保持整个应用的统一,还是需要在代码里进行设置; 例如,上面个人中心的页面中,头像距离屏幕的左边距其实在全局已经定义为一个常量值(一般视觉都会定一些全局性的规范),但是 Xib 中设置 frame 无法直接引用已定义的常量;

Xib 确实是一种不错的方法,简单方便,但从上面分析可看出 Xib 也存在一些问题。本文对 Xib 不再进行深入讨论,一是个人喜好,另外主要原因是工作中对 Xib 使用并不多, 所以可能有些东西尚未深入研究,无法过多的讨论。

使用 UIScrollView

有些同学写这类页面时可能会选择一个不怎么好的方案:将页面所有的 View 作为 UIScrollView 的子View。

代码举例

这样编写这类页面时,controller 的代码基本类似于下面这样:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadSubviews];
}

- (void)loadSubviews {
    // scrollView 作为所有子View的容器
    _scollView = [[UIScrollView alloc] init];
    [self.view addSubview:_scrollView];

    _view1 = [[UIView alloc] init];
    [_scrollView addSubview:_view1];

    // 添加其余子View,并为每个View设置约束(如果不使用约束,则也需要在 viewWillLayoutSubviews 方法中为各个View设置 Frame)
    // 此外,还要定义各类点击行为和点击效果。
}

方案缺点分析

根据经验来看,这类写法基本不可取,主要原因有以下几点:

  • 当页面内容比较多的时候,子View数量会很多,这样一方面由于添加子View的代码过多引起 Controller 变得庞大,另一方面子View很多时,对子View的布局容易出现错误,不易排查。
  • 针对第一点,可能会有开发人员将子View按行进行分块,并封装成一个较大的 View,这样会减少 UIScrollView 直接管理的子View数量,一定程度上避免了第一点问题。 但是,当设计师需要移动不同行的顺序,或者在某一位置添加了一行新的内容,这时候修改布局大概会令人吐血了吧。
  • 如果某一行视图需要点击,则需要为该行视图添加手势或者采用 UIButton,并设置好点击的回调;当页面视图行数较多时,会导致代码杂乱。

所以,对于随业务需求修改极有可能导致页面布局和内容变化的页面,我们不能使用 UIScrollView 来管理子View。那么什么时候可以使用 UIScrollView 来管理页面中的视图呢? 这里给出两个条件:

  • 当页面内容很少,并且页面需要像 UIScrollView 一样可以滚动。
  • 页面内容在一定时期内基本不太可能修改的页面。一般常见于非主要业务的页面,如反馈页面,评论页面。

当满足这两个条件时,建议采用 UIScrollView 来管理页面中的视图,这也是最简单的方案,如下图某应用的反馈页面:


使用 UITableView

另一种方案是将页面中每一行看作为 UITableView 的一个 Cell,通过 UITableView 来组织这类页面中的各个视图。

代码举例

下面让我们以 demo 中的页面为例,来看一下这种方案写出的 Controller 代码是什么样子的:

(1) 先介绍一下 demo 页面,如下图:


整个页面是以 UITableView 实现的,分为两个 section:cell1(类型为 MCDemoCell1)和 cell2(类型为 MCDemoCell2) 为 section1,cell3(类型为 MCDemoCell3) 为 section2。

(2) 首先,我们要新建一个 UITableView,然后为其设置 dataSource 和 delegate,以及确定其布局,代码如下:

#pragma mark - Life cycle.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"MultiCellTypeTableViewOC";
    self.view.backgroundColor = [UIColor whiteColor];

    [self loadSubviews];
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    _tableView.frame = self.view.bounds; // 对 tableView 布局
}

#pragma mark - Load views.

- (void)loadSubviews {
    _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
    _tableView.delegate = self; // 设置 tableView 的 delegate
    _tableView.dataSource = self; // 设置 tableView 的 dataSource
    [self.view addSubview:_tableView];
}

(3) 实现 UITableViewDataSource 的方法:

#pragma mark - UITableViewDataSource.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // 返回 section 的数量,本例中为 2
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // 根据 section 区分有几个 cell
    if (section == 0) {
        return 2;
    } else {
        return 1;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = nil;
    // 根据 indexPath 获取不同的 Cell
    if (indexPath.section == 0) {
        if (indexPath.row == 0) {
            cell = [self getCell1];
        } else {
            cell = [self getCell2];
        }
    } else {
        cell = [self getCell3];
    }

    return cell;
}

上述代码中获取不同 Cell 的方法是私有方法,主要就是创建一个类型的 Cell,然后为该 Cell 进行配置数据,下面是 getCell2 的例子:

- (UITableViewCell *)getCell2 {
    MCDemoCell2 *cell = [[MCDemoCell2 alloc] init];
    cell.titleLabel.text = @"cell2";
    cell.contentLabel.text = @"cell2's content";
    cell.rightSwitchButton.on = YES;
    cell.delegate = self;
    return cell;
}

    (4) 实现 UITableViewDelegate 的方法:

#pragma mark - UITableViewDelegate.

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 20;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    CGFloat cellHeight = 0;
    // 根据 indexPath 获取不同 Cell 的高度
    if (indexPath.section == 0) {
        cellHeight = 44.0;
    } else {
        cellHeight = 80.0;
    }
    return cellHeight;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    // 如果选中不同 cell 的动作不一致,则需要使用判断执行对应的操作
    if (indexPath.section == 0) {
        if (indexPath.row == 0) {
            NSLog(@"cell1 selected");
        } else {
            NSLog(@"cell2 selected");
        }
    } else {
        NSLog(@"cell3 selected");
    }
} 

这种方案的详细代码见 demo 的 OldViewController。


本文未结束,敬请期待下篇。

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

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


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