系列:iOS开发-UITableView

系列:iOS开发-UITableView

连续工作好几天,终于再次空闲下来了,今天开始,我们再继续说说iOS开发中最常用的控件,也是稍微有点复杂的控件,UITableView,
简单的说他就是一个表格控件,
我们在以前使用OFFICE中的EXCEL时进程会看到,多行多列的一个表格,这个时候我们把需要的数据分别填进相应的地方,做出一个比较好的布局.
这样的好处是方便直观,并且可以做出相应的比较或者批量的编辑等…
可以对类似的东西做出一些列的选择或者对比…
好处很多很多.
同样的我们在使用手机应用中经常可以看到这样的东西,最常见的就是iOS手机的设置应用,这里写图片描述

很直观很方便,是不是,我们可以把一类的东西都整理到一起,做成一个表格,这样在操作的时候我们就很直观的处理,比如这个设置,我们可以在里面观看各种功能的状态,可以设置各种功能的开关…

再来其他的应用,正好我正准备点餐,我打开了一个点餐的APP
我看到了这样的界面
这里写图片描述
中午吃个龙虾盖浇…好吧跑题了….
我们可以看到,其实它跟设置类似,只是现在不是表格的每一行不是某项功能了,而是某一个商家,里面的信息更加丰富,有商家的logo,有商家的店名,距离,优惠….我们可以在一行中看到很多有效的信息.
但其实把它抽象一下,都是一样的,它就是一行数据,在表格中的不同呈现方式而已…
所以说,表格控件在移动端的开发是必不可少的.几乎每个应用都会用到,或多或少,少的可能就是一个两个,多的甚至几乎整个应用都是使用表格控件,所以掌握它是必须的.
我们先从简单的开始,说明表格的基本原理和逻辑, 之后再尝试着丰富表格的每一行,做到类似于点餐app的那个好看的界面…
在这里因为没有讲到MVC\MVVM等开发模式,我们就用最普通的逻辑,之后再开发中再进行优化…

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即可,
那么就会有这样的协议需要我们去实现.
这里写图片描述
当然并不是全部都要实现的,所以它分为require和option两种,前者必须实现协议,后者可选.
我们先看必须实现的,简单理解下,第一个是每组多少行的方法,第二个是每行的内容.
是了.作为表格这肯定是必须的,OK
我们从头至尾依次实现看看

//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
}

这里写图片描述
看不出什么效果,我们拖动看看
这里写图片描述
看到效果了么?后面的几行并不是之后创建的,而是直接使用了上面0-4行不在屏幕内的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];
}

我们可以看到类似这样的界面
这里写图片描述
试着按住3条横线的地方拖动看看,可以交换任意两行的位置哟.
当然我们要在界面上交换了之后做出相应的处理,需要修改数据已到达相匹配,否则交换的是没有效果的…

好了基本上一个表格控件就学完了,
那么我们能不能仿照刚才的点餐软件制作一个不一样的表格呢?
我们分析看看
这里写图片描述
这是其中的一行,其实每一行都是类似的,只是其中的图片,文字不一样而已

我们这里先不弄那么复杂,我大致做出一个框架,这里我们有几种方式,
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地址:https://github.com/spicyShrimp/DEMO_OC

系列:iOS开发-前言+大纲
http://blog.csdn.net/spicyShrimp/article/details/62218521

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值