#系列:iOS开发-UITableView
连续工作好几天,终于再次空闲下来了,今天开始,我们再继续说说iOS开发中最常用的控件,也是稍微有点复杂的控件,UITableView, 简单的说他就是一个表格控件, 我们在以前使用OFFICE中的EXCEL时进程会看到,多行多列的一个表格,这个时候我们把需要的数据分别填进相应的地方,做出一个比较好的布局. 这样的好处是方便直观,并且可以做出相应的比较或者批量的编辑等... 可以对类似的东西做出一些列的选择或者对比... 好处很多很多. 同样的我们在使用手机应用中经常可以看到这样的东西,最常见的就是iOS手机的设置应用,
很直观很方便,是不是,我们可以把一类的东西都整理到一起,做成一个表格,这样在操作的时候我们就很直观的处理,比如这个设置,我们可以在里面观看各种功能的状态,可以设置各种功能的开关...
再来其他的应用,正好我正准备点餐,我打开了一个点餐的APP 我看到了这样的界面
OK 首先是创建一个tableview对象,这里我们就犹豫了,我们是一行一行的创建?那学这个干什么,之前的基础控件就搞定了啊.. 这里我解释一下原理,之前我们有学习过scrollerView,它能够实现类似于网页一样的拖动,以呈现超出屏幕或者控件范围的更多的东西,我们把表格就看成这样的一个控件,我们在scrollerView中一行一行添加内容,并且设置水平不能拖动,只能竖直拖动,那么它就是一个表格...
所以iOS的表格就是这样的方式创建的,我们可以看到UITableView就是继承自UIScrollView.(所以UIScrollView的所有方法,UITableView都可以拿过来用...)
NS_CLASS_AVAILABLE_IOS(2_0) @interface UITableView : UIScrollView <NSCoding>
复制代码
一些基础属性我们就暂且不表了.都能够看懂意思,比如背景色啊什么的. OK,我们仿照scrollerView创建对象就是了.
//style:UITableViewStylePlain和UITableViewStyleGrouped(可以分别尝试看看区别)
_tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];//创建对象
_tableView.delegate = self;//遵循UITableViewDelegate
_tableView.dataSource = self;//遵循UITableViewDataSource
[self.view addSubview:_tableView];//添加到视图中
复制代码
对象创建完成,由于它是一个表格,有两部分构成,一部分是每一行的内容,我们把它看成数据部分,还有一层是底层scrollerView的拖动,负责界面的显示逻辑. 所以我们要分别处理这两块,比如我们想在滑动到某个位置时做一些特殊事情等等,我们就需要遵循scrollerView的delegate,当然因为现在是tableview,它又做了2次封装,有一个新代理@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
我们遵循它也是一样的它专门为tableView服务,同时兼有scrollerview的所有...
OK 表格底层创建完成了,我们怎么添加每一行呢? 创建一个对象添加一下,并且设定坐标? 其实差不多,这是这里没有那么复杂,我们只要确定有多少组,每组有多少行,每行有多高就好了,那么他就能够自动自上而下的布局了.至于怎么布局? 我们遵循tableVIew.dataSource即可, 那么就会有这样的协议需要我们去实现.
//tableview组数//默认可以不写,默认是1组
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
//tableview每组对应行数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _dataArray.count;
}
//tableview的每一行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [[UITableViewCell alloc]init];
cell.textLabel.text = _dataArray[indexPath.row];//系统cell默认的文本框.可以直接赋值,从数组中取出对应的行的数据
return cell;//返回每行构建成功的cell
}
复制代码
至于_dataArray? 它是一个数组,我再构建tableview之前已经写好了
_dataArray = [NSMutableArray array];
for (int i = 0; i<20; i++) {
NSString *str = [NSString stringWithFormat:@"第%d行数据",i];
[_dataArray addObject:str];
}
复制代码
它是存储了tableview需要展示的内容,当然不一定非要在之前就有数据,我们通常网络请求的时间很长,我们就可以先创建tableview之后请求数据回来之后,刷新一下表格,将数据再放进去,让表格重新布局就好了 使用[_tableView reloadData];
即可
OK 一个最简单的表格就创建完成了,当然现在的表格是只有展示,没有其他能力的. 这个稍后再讲解, 再次之前我们需考虑一件事情,一个表格有30行,这样我创建一个表格需要加载30个cell,不算多,创建30次就好了, 但是一个表格如果展示的是很多数据呢?比如9000行,比如100000行,这样的可以吗?我们在创建的时候创建100000个cell,刷新的时候刷新100000行数据? 这样会不会很浪费时间和系统资源? 那么有没有什么好的方法呢? 其实iOS在设计tableView的时候就考虑到这个问题,它使用了一种巧妙的方式避免了,什么方式呢? 我们有10000行,但是一个手机屏幕才多大?只能显示10来行而已.,那么我们就创建10来行显示就好了,当滑动到下面的时候,我们尝试看看缓存池里面有没有相同的cell,有的话就拿过来直接使用,没有的话就另行创建,至于不在屏幕中的cell,我们也没有必要立马销毁,我们把它放在缓存池中,等待下次使用,避免再创建新的cell 这样就形成了一个良性循环,真正创建的cell就那么10来个,一直重复使用,性能消耗也就是那么点... 我们看看具体实现的方式
//tableview的每一行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// UITableViewCell *cell = [[UITableViewCell alloc]init];
static NSString *cellid = @"cellid";//静态的一个重用id
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid];//根据id从缓存池中获取cell
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid];//如果没有获取成功,则手动创建一个cell 重用id为敌营的id
}
cell.textLabel.text = _dataArray[indexPath.row];//系统cell默认的文本框.可以直接赋值,从数组中取出对应的行的数据
return cell;//返回每行构建成功的cell
}
复制代码
可以看到创建的时候的方式变了一下,之前直接创建,现在改成了创建的时候同时携带一个复用id,之后需要的时候根据这个id到系统里面去找, 你可能会怀疑,这样的话就有效果了么?我使用了这样的方式,形成的表格没有任何变化啊,我们换个方式显示一下你就能看到了, 我们在创建cell的时候同事给它一个tag值,之后直接显示出来,在复用的时候不修改,直接显示tag看看
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// UITableViewCell *cell = [[UITableViewCell alloc]init];
static NSString *cellid = @"cellid";//静态的一个重用id
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid];//根据id从缓存池中获取cell
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid];//如果没有获取成功,则手动创建一个cell 重用id为敌营的id
cell.tag = indexPath.row;
}
// cell.textLabel.text = _dataArray[indexPath.row];//系统cell默认的文本框.可以直接赋值,从数组中取出对应的行的数据
cell.textLabel.text = [NSString stringWithFormat:@"我是在第%ld行创建的",(long)cell.tag];
return cell;//返回每行构建成功的cell
}
复制代码
说了这么多,我们基本上的一个表格控件的展示就讲完了,你会说没有什么复杂的啊. 那么稍微说点复杂的 我们继续看看接下来的某些协议
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{
return YES;
}
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewCellEditingStyleDelete;
}
复制代码
我设置了可以编辑的模式,我们把其中一行向左拖一下看看
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
if (editingStyle == UITableViewCellEditingStyleDelete) {
[_dataArray removeObjectAtIndex:indexPath.row];
[_tableView reloadData];
}
}
复制代码
当然还有插入的方式和删除类似, 在这里我在说以个交换的功能
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath{
return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
[_dataArray exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
[_tableView reloadData];
}
复制代码
我们可以看到类似这样的界面
好了基本上一个表格控件就学完了, 那么我们能不能仿照刚才的点餐软件制作一个不一样的表格呢? 我们分析看看
我们这里先不弄那么复杂,我大致做出一个框架,这里我们有几种方式, 1:创建一个系统的cell 在cell中添加imageView label等控件.. 2:创建一个新的继承自系统cell的子类,实现自定义的cell 当然我们因为开发的需求,我们理论上选择第二种会多一些,封装成一个自定义的cell这样,不会跟系统的弄混,同时也把cell的创建等抽离出去,避免代码的混乱. 此外随时可以二次开发,另行定制... 当然自定义是什么意思?其实简单的理解就是,你在一个view上面添加一些基础控件或者其他的,组合成一个新的类,这个类有着独有的一些方法或者响应的事件,这样的一个类就是你自定义出来的一个控件,类似于button,他就是一个label,两个imageView组合而成的,添加上一个点击的响应事件,组合成的一个新的控件, 所以我们自定义cell也是如此
当然我们自定义,我们会选择几种方式,xib,storyboard,手写... 这里呢,因为是初学,我们或许会觉得xib,storyboard拉拉拽拽就好了.没有那么麻烦.但是我还是觉得初学的时候多手写更好,等熟悉了,我们再谈可视化的那些. OK 我们创建一个继承系统cell的子类
_dataArray = [NSMutableArray array];
for (int i = 0; i < 20; i++) {
NSDictionary *dict = @{@"logo":@"logo.png",@"name":@"必胜客",@"saleCount":@"500份",@"desc":@"0元起送/配送费9元",@"distance":@"400m"};
[_dataArray addObject:dict];
}
复制代码
接下来是自定义的子类cell
最后在创建cell的地方修改一下
//tableview的每一行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// UITableViewCell *cell = [[UITableViewCell alloc]init];
static NSString *cellid = @"cellid";//静态的一个重用id
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid];//根据id从缓存池中获取cell
if (!cell) {
cell = [[MyTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid];//如果没有获取成功,则手动创建一个cell 重用id为敌营的id
cell.tag = indexPath.row;
}
// cell.textLabel.text = _dataArray[indexPath.row];//系统cell默认的文本框.可以直接赋值,从数组中取出对应的行的数据
// cell.textLabel.text = [NSString stringWithFormat:@"我是在第%ld行创建的",(long)cell.tag];
NSDictionary *dict = _dataArray[indexPath.row];
cell.logoImageView.image = [UIImage imageNamed:dict[@"logo"]];
cell.storeNameLabel.text = dict[@"name"];
cell.starImageView.image = [UIImage imageNamed:dict[@"star"]];
cell.saleCountLabel.text = dict[@"saleCount"];
cell.saleDescriptionLabel.text = dict[@"desc"];
cell.distanceLabel.text = dict[@"distance"];
return cell;//返回每行构建成功的cell
}
复制代码
运行看看?
效果出来了! 虽然很简单,但是这个是本地随便模拟出来的数据,如果换成真正的网络数据呢?那肯定就不一样了... 当然我们是不是觉得cell里面赋值有点复杂?我们可以把赋值的那部分写成cell的一个方法
-(void)setContentWithDict:(NSDictionary *)dict{
_logoImageView.image = [UIImage imageNamed:dict[@"logo"]];
_storeNameLabel.text = dict[@"name"];
_starImageView.image = [UIImage imageNamed:dict[@"star"]];
_saleCountLabel.text = dict[@"saleCount"];
_saleDescriptionLabel.text = dict[@"desc"];
_distanceLabel.text = dict[@"distance"];
}
复制代码
这样我们在cell赋值的地方只要这样就好了
//tableview的每一行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellid = @"cellid";
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid];
if (!cell) {
cell = [[MyTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid];
}
NSDictionary *dict = _dataArray[indexPath.row];
[cell setContentWithDict:dict];
return cell;
}
复制代码
OK,看似没有什么原理和必要,但是其实我们是把数据单独写在了一个地方_dataArray,这部分我们可以看成是一个数据的模型. cell部分呢?他就是一个视图,vc部分负责链接这两部分... 其实这就是一个最简单的MVC的开发模式.当然,对于MVC开发来说并没有这么简单,可以说的还有很多,我会再详细描述的
好了,UITableView的基础学习基本就完结了,学会了基础的方法,我们后续只要在这个基础上再做一些稍微复杂的运算或者逻辑,就能够形成很多特殊的功能了, 比如点击某个cell 我们会触发一个响应,比如我们在cell里面添加一个开关,比如在cell里面添加一个滑块?当然,这些都不是问题.......
Demo地址:github.com/spicyShrimp…
系列:iOS开发-前言+大纲 blog.youkuaiyun.com/spicyShrimp…