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
的完整实现(详细代码略), 代码中做了以下几件事情:
-
ViewController
准守了UITableViewDataSource
协议; -
mtableview
的dataSource
属性赋值self
; -
ViewController
实现了两个方法;
代码运行效果:
图整个区域为tableview
, 展示出了10条hello cell
数据;从中我们可以直观感受到:
-
numberOfRowsInSection
方法确定了列表的数据个数; -
cellForRowAtIndexPath
返回了每条数据的内容;
虽然这是tableview
最简单的使用, 也没有什么实际的应用场景, 但是它揭示了控件的基本运行逻辑, 从中我们能够看出控件到底是做什么用的, 并且为进一步学习和使用提供了一个很好的思路:
- 如何针对不同行返回不同的数据?
- 分组的数据应该如何显示?
- 每条数据的高度是怎么确定的?宽度是否可变?
- 每一行都会有个view来显示数据, 这个view是否可以重用?
- 如果每条数据中不只有一行文本,还想显示图片和副标题能不能办到?
- 更复杂的数据显示?
- 对数据的增删改怎么实现?
- 与用户的交互
我们以上述思路为提纲开始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;
}
对于很多教程来说,上述代码都是是不推荐的,他们会说对于数据集我们应该单独存放,用的时候直接取值,这样将数据和使用放到一起是不符合规范的。 那为什么我还要写这样的代码呢?有两个原因:
代码可用!这是一个可用的代码,从其中我们能够更直接的理解
cellForRowAtIndexPath
的作用, 该方法通过一个indexPath
来表示所处行, 并根据该值返回不同的数据!代码本质! 所谓的规范是为了更好(没有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 设置了高度也没有效果, 这是我们需要修改tableview
的 sectionHeaderHeight
属性.
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
方法返回的高度为准.
未完待续...