系列: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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容