UITableView 的前前后后

2017-05-12  本文已影响25人  votzone

UITableView 可能是应用开发中最常用的一个控件了; 几乎任何一个应用都会需要显示一些列表的信息, 那就必然会用到显示列表的控件; 本文小田将以自己学习的角度尽可能详细分析UITableView, 并且尽量以非术语的方式和推理的方式来验证问题和知识点.

初体验

先看短代码


#import "ViewController.h"
@interface ViewController ()<UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITableView *mytableview;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
    self.mytableview.dataSource = self;
}

// 每一组有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return 10;
}

// 每一行显示的内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
  
  cell.textLabel.text = @"hello cell";
  
  return cell;
}
@end

如上是一个Controller 的完整实现(详细代码略), 代码中做了以下几件事情:

  1. ViewController准守了UITableViewDataSource协议;
  2. mtableviewdataSource属性赋值self;
  3. ViewController 实现了两个方法;

代码运行效果:

20170511149451690223861.png

图整个区域为tableview, 展示出了10条hello cell数据;从中我们可以直观感受到:

  1. numberOfRowsInSection 方法确定了列表的数据个数;
  2. cellForRowAtIndexPath 返回了每条数据的内容;

虽然这是tableview 最简单的使用, 也没有什么实际的应用场景, 但是它揭示了控件的基本运行逻辑, 从中我们能够看出控件到底是做什么用的, 并且为进一步学习和使用提供了一个很好的思路:

  1. 如何针对不同行返回不同的数据?
  2. 分组的数据应该如何显示?
  3. 每条数据的高度是怎么确定的?宽度是否可变?
  4. 每一行都会有个view来显示数据, 这个view是否可以重用?
  5. 如果每条数据中不只有一行文本,还想显示图片和副标题能不能办到?
  6. 更复杂的数据显示?
  7. 对数据的增删改怎么实现?
  8. 与用户的交互

我们以上述思路为提纲开始UITableView的解析.

一. 如何针对不同行返回不同的数据?

这应该是最基本的问题, 如果一个tableview不能根据行展示不同的数据,那么它可能就没存在的必要了. 从之前的代码中也能推测出, 我们应该在cellForRowAtIndexPath 方法中为每个indexPath 返回一个不同 cell. 于是有了如下代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    
        NSString * text = nil;
    switch (indexPath.row) {
        case 0:
            text = @"hello 0";
            break;
        ...
        ...
        case 9:
            text = @"hello 9";
            break;
        default:
            break;
    }
    cell.textLabel.text = text;
    return cell;
}

对于很多教程来说,上述代码都是是不推荐的,他们会说对于数据集我们应该单独存放,用的时候直接取值,这样将数据和使用放到一起是不符合规范的。 那为什么我还要写这样的代码呢?有两个原因:

  1. 代码可用!这是一个可用的代码,从其中我们能够更直接的理解cellForRowAtIndexPath的作用, 该方法通过一个indexPath来表示所处行, 并根据该值返回不同的数据!

  2. 代码本质! 所谓的规范是为了更好(没有bug, 包括功能bug和性能bug等)和更快(用时少)的实现一个需求,但规范并不能解决逻辑本质, 如果逻辑能够量化,代码中逻辑是守恒的, 准守规范不意味着不需要实现某些逻辑! 如上述代码中,如果准守规范将所有数据放入一个NSArray中,在用的时候通过下标取值,其逻辑与上述代码中的实现是相同的.

到此我们第一个问题的答案已经出现了, cellForRowAtIndexPath方法中,根据indexPath的值不同,返回不同的数据内容, 可以实现不同行返回不同的数据.

二.分组的数据应该如何显示?

直接上代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
    NSString * text = [NSString stringWithFormat:@" hello in section %ld, row %ld", indexPath.section, indexPath.row];
    cell.textLabel.text = text;
    return cell;
}

indexPath中有 row 属性, 同时还有一个section属性,表示当前返回的cell 属于 第section组的第row行. 可以根据这个值来确定当前的数据内容.

那么如何知道有多少组(section) 每个组有多少行数据呢?

// 返回多少组
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2;
}
// 返回对应section 中有多少行 row
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    switch (section) {
        case 0:
            return 3;
        case 1:
            return 2;
        default:
            break;
    }
    return 0;
}

好了,我们现在可以显示多个组的数据了, 当我们想为不同的组添加一个标题时 可以使用titleForHeaderInSection 或者viewForHeaderInSection 方法:

// 设置纯文本 的section header
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [NSString stringWithFormat:@"section header at %ld", section];
}

//设置section view, 拜托系统header限制,自定义样式;
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UIView* myView = [[UIView alloc] initWithFrame:CGRectMake(10, 0, 300, 50)];
    myView.backgroundColor = [UIColor colorWithRed:0.10 green:0.68 blue:0.94 alpha:0.7];
    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 22)];
    titleLabel.textColor=[UIColor whiteColor];
    titleLabel.backgroundColor = [UIColor clearColor];
    titleLabel.text=[NSString stringWithFormat:@"section header at %ld", section];
    [myView addSubview:titleLabel];
    
    return myView;
}

经测试如上两个设置section header 的方法不能共存, 当同时实现时viewForHeaderInSection的优先显示.

最后, 我们会发现viewForHeaderInSection中没有办法设置section header 的高度,即便返回的view 设置了高度也没有效果, 这是我们需要修改tableviewsectionHeaderHeight 属性.

sectionHeaderHeight 属性是属于tableview的, 不会根据section 的不同而不同, 因此只需要在数据加载之前设置一次即可!

viewForHeaderInSection 中添加tableView.sectionHeaderHeight = 100; 时, 代码是没有效果的, 因此可以推断 tableview在加载数据过程中 section header的大小已经固定了, 再设置是不起作用的.

tableview 针对不同Section 返回不同头部高度的方法heightForHeaderInSection

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    // 根据section 返回不同的高度
    return 50*section +50;
}

经测试同时设置sectionHeaderHeight属性和实现heightForHeaderInSection方法, 会以heightForHeaderInSection方法返回的高度为准.

未完待续...

上一篇下一篇

猜你喜欢

热点阅读