什么是UITableView:T- UITableView继承自UIScrollView ,所以可以滚动(只支持垂直滚动)查看里面展示的内容。在iOS开发中,UITableView是展示大量内容的首选
- 一般来说进行大量的有规则排列的数据首先考虑使用UITableView
- UITableView具有重用和延迟加载等特性,可以有效的提高效率(它并不会一次性的将所有数据进行加载,而只是加载当前视图中可见的数据)
- UITableView的每一行数据就是一个UITableViewCell,UITableView只能有一列数据(cell),且只支持纵向滑动,当创建好的tablView第一次显示的时候,我们需要调用其reloadData方法,强制刷新一次,从而使tableView的数据更新到最新状态
UITableView的几个重要属性
- datasource: 是UITableViewDataSource类型,主要为UITableView提供显示用的数据,指定UITableViewCell支持的编辑操作类型(insert,delete和reordering),并根据用户的操作进行相应的数据更新操作
- delegate: 是UITableViewDelegate类型,主要提供一些可选的方法,用来控制tableView的选择、指定section的头和尾的显示
- rowHeight:设置每一行数据的高度
- UITableView的style属性:
- plain:不分组,所有数据都属于同一个组
- grouped:分组,会显示有默认的头部块和底部块
- 一般情况下我们会设置当前控制器为UITableView的datasource和delegate,理由:根据MVC设计模式,view是与controller进行交互的。而UITableView是View,UIViewController是Controller,所以展示数据的view需要数据就会调用controller的数据源方法获取数据,而不会去关注数据如何获取。简单的说,在UITableView中,在要展示数据的时候,就会询问它的数据源到底需要显示多少组数据,每一组数据中有几行数据,每一行数据具体的内容是什么,控制器就负责实现这些方法,而UITableView只负责显示。控制器负责获取数据,UITableView负责展示 ,这就是mvc的好处。
UITableView常用数据源方法
在展示大量数据的时候我们需要考虑:1.全部的数据一共可以分为多少组 2.每一组有多少条数据 3每一条数据的具体内容是什么。基于这三点我们需要掌握下面数据源方法
1. -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView:这个方法用来确定当前UITableView一共有几组数据,如果没有实现这个数据源方法,那么系统默认只有一组数据,也就意味着这个方法默认返回1.如果有多组数据,那么就需要在这个方法里面进行确定。
2. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section:这个方法确定在对应的组中有几条(行)数据,必须在实现这个方法,否则数据无法显示。当每一次创建组的时候都会调用这个方法确定该组的数据行数量。
3.- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;这个方法确定某个组中某一行显示的具体的内容,在UITableView中,真正的数据显示在cell中,所以为cell的某些属性赋值才能真正的让数据展示出来。在创建每一组每一行显示的数据的时候都会调用这个方法,这是必须实现的方法。
4. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section:设置头部文本,返回默认样式的文本
5. - (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section:设置底部文本,返回默认样式的文本
重要说明—什么时候使用数据源方法:一般情况下与数据显示相关的,或者与UITableView默认的显示样式相关的,一般都是数据源方法(当然有例外)
使用UITableView展示简单自定义数据
效果:
UITableViewCell的三个重要属性
- imageView:默认在最左边显示的小图像,可以不设置,如果没有则不显示图片
- textLabel:显示的文本,一般情况下显示的文本都是使用这个属性
- detailTextLabel:显示的详细介绍,一般字体大小比textLabel小,如果不设置则不显示。
- 图示:
UITableViewCell的样式(style):
这是一个只读属性,只有在创建的时候可以设置。
- UITableViewCellStyleDefault:该格式提供了一个简单的左对齐的文本标签textLabel和一个可选的图像imageView。如果显示图像,那么图像将在最左边。这种格式虽然可以设置detailTextLabel,但是不会显示该标签。
- UITableViewCellStyleSubtitle:该格式与前一种相比,增加了对detailTextLabel的支持,该标签将会显示在textLabel标签的下面,字体相对较小。
- UITableViewCellStyleValue1:该格式居左显示textLabel,居右显示detailTextLabel,且字体较小
- UITableViewCellStyleValue2:该格式居左现实一个小型蓝色主标签textLabel,在其右边显示一个小型黑色副标题详细标签detailTextLabel,该格式不支持图像
- 第一种和第二种样式使用相对更多。
- 图例:
UITableViewCell的accessoryType属性
- UITableViewCellAccessoryNone;//cell没有任何的样式
- UITableViewCellAccessoryDisclosureIndicator;//cell的右边有一个小箭头,距离右边有十几像素
- UITableViewCellAccessoryDetailDisclosureButton;//cell右边有一个蓝色的圆形button和一个>
- UITableViewCellAccessoryDetailButton://cell右边有一个小圆圈,里面有倒立的叹号
- UITableViewCellAccessoryCheckmark;//cell右边的形状是对号
实现过程:
- 在stotyboard上添加UITableView控件
- 让控制器遵守协议:@interface ViewController ()<UITableViewDataSource>
- 指定UITableView的数据源为当前控制器:self.tableView.dataSource=self;
- 实现相应的数据源方法
//确定UITableView所展示的数据的组的数量,如果没有实现这个方法,那么默认是1组数据
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;//设置一共有2组数据
}
//确定UITableView每一组中行的数量
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//section就是代表某个组 如有两个组,那么section的值就是0和1
if (section==0) {
return 1; //第一组有一行数据
}
else
{
return 2;//第二组有两行数据
}
}
//确定每一行数据具体内容-确定cell的内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//创建cell
UITableViewCell *cell=[[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:nil];;
if(indexPath.section==0)//说明是第1组,只有一行,所以不再需要判断是那一行
{
cell.textLabel.text=@"第一组第一行";
}
else if(indexPath.section==1)//第一组
{
if(indexPath.row==0)//第一组第一行
{
cell.textLabel.text=@"第二组第一行";
}
else if(indexPath.row==1)//第二组第二行
{
cell.textLabel.text=@"第二组第二行";
}
}
cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- 设置UITableView的其它属性
- 重点说明NSIndexPath:用来标识当前cell的在tableView中的位置,该类别有section和row两个属性,前者标识当前cell处于第几个section中,后者代表在该section中的第几行
UITableView的常用代理方法
常用方法:
1. - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath:在cell被选择的时候调用这个方法,我们可以在这个方法里面对选择的cell进行相关的操作
2. - (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section:可以为头部设置自定义的view.它需要我们创建自定义的view.
补充说明:
- 使用代理方法需要遵守UITableViewDelegate
- 什么时候使用代理方法:一般情况下如果对cell所展示的数据进行操作的时候或者返回一些自定义的样式或者元素的时候
使用UITableView读取plist文件展示单组汽车品牌数据
效果图:
创建汽车模型类:
- 声明获取模型对象的实例方法和类方法以及获取所有模型数组的方法
@interface WHCar : NSObject
@property (nonatomic,copy)NSString *title;
@property (nonatomic,copy)NSString *desc;
@property (nonatomic,strong)NSArray *cars;
//实例方法:获取模型对象
- (instancetype) initWithDic:(NSDictionary *)dic;
//类方法:获取模型对象
+ (instancetype) carWithDic:(NSDictionary *)dic;
//获取所有模型数据,以数组的形式返回
+ (NSArray *) cars;
@end
- 实现.h文件中声明方法
@implementation WHCar
//实例方法:返回模型对象(使用了kvc)
- (instancetype)initWithDic:(NSDictionary *)dic
{
if(self=[superinit])
{
[self setValuesForKeysWithDictionary:dic];
}
return self;
}
//类方法:获取模型对象
+ (instancetype)carWithDic:(NSDictionary *)dic
{
return [[WHCaralloc] initWithDic:dic];
}
//返回所有模型数据,以数组形式返回
+ (NSArray *)cars
{
NSArray *sourceArr=[NSArrayarrayWithContentsOfFile:[[NSBundlemainBundle] pathForResource:@"cars_simple.plist"ofType:nil]];
NSMutableArray *desArr=[NSMutableArrayarray];
for (NSDictionary *dicin sourceArr) {
WHCar *car=[WHCarcarWithDic:dic];
[desArr addObject:car];
}
return desArr;
}
@end
控制器中的代码实现:
- 模型数组属性的声明
//模型数组
@property (nonatomic,strong)NSArray *cars;
- 数据懒加载
//数据懒加载
- (NSArray *)cars
{
if(_cars==nil)
{
_cars=[WHCarcars];
}
return _cars;
}
控制器遵守UITableViewDataSource协议,指定UITableView的DataSource
@interface ViewController () <UITableViewDataSource>
.......
// 指定数据源
self.tableView.dataSource=self;
实现数据源方法
- 确定组的数量
//数据源方法:确定组的数量,如果没有实现这个方法,那么默认就1组数据
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
//每一个模型数据形成一个分组
return self.cars.count;
}
- 确定每一个组行的数量
//数据源方法:确定每组的行数
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
WHCar *car=self.cars[section];
//每一个分组里面的行数就是模型数据中cars数组的长度
return car.cars.count;
}
- 确定每一个组每一行所显示的cell
- 获取数据模型
- 创建cell
- 为cell的属性赋值
- 代码:
//数据源方法:创建cell,同时为cell赋值
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//获取模型数据
WHCar *car=self.cars[indexPath.section];
//创建cell
UITableViewCell *cell=[[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:nil];
//为cell赋值
cell.textLabel.text=car.cars[indexPath.row];
//设置cell的样式 cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
//返回创建好的Cell
return cell;
}
- 实现UITableView的两个数据源方法:可以设置拥有默认字体的头部和底部文本
- - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section:用于设置每个组头部所显示的文本
//数据源方法:设置组的头部文本
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
//获取模型数据
WHCar *car=self.cars[section];
//返回当前组头部所需要显示的文本值
return car.title;
}
- - (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section:用于设置每个组底部所显示的文本
//数据源方法:设置组的底部文本
- (NSString *) tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
//获取模型数据
WHCar *car=self.cars[section];
//返回当前组头部所需要显示的文本值
return car.desc;
}
使用UITableView展示LOL数据
效果图:
根据plist文件创建模型类:
- 声明获取模型对象的实例方法和类方法以及获取所有模型数组的方法
- 实现声明的方法
控制器中的代码实现:
- 模型数组属性的声明
- 数据懒加载
控制器遵守UITableViewDataSource代理协议,指定UITableView的DataSource
实现数据源方法
- 确定组的数量
- 确定每一个组行的数量
- 确定每一个组每一行所显示的cell
重点:UITableView的性能优化(cell的循环利用)
- 为什么需要优化:iOS设备的内存有限,如果用UITableView显示成千上万条数据,就需要成千上万个UITableViewCell对象的话,那将会耗尽iOS设备的内存。要解决该问题,需要重用UITableViewCell对象
- 优化原理:当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放入一个对象池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCell,dataSource会用新的数据配置这个UITableViewCell,然后返回给UITableView,重新显示到窗口中,从而避免创建新对象,这就节省了创建新对象的时间,也避免了反复的内存分配及开销。
- 如何进行cell的重用:可以在初始化UITableViewCell的时候传入一个特定的字符串标识来设置reuseIdentifier(一般用UITableViewCell的类名)。当UITableView要求dataSource返回UITableViewCell时,先通过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,如果有,就重用,如果没有,就传入这个字符串标识来初始化一个UITableViewCell对象,以便下一次重用。
- 代码:
//数据源方法:创建cell
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//定义重用标识
NSString *ID=@"hero";
//从缓存中创建cell,而不是直接创建一个新的cell,查找缓存,如果缓存中存在可被重用的cell,那么就不会再重新创建cell,避免了反复的创建和回收,节省资源
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:ID];
//缓存中有可能并不存在可被重用的cell(如第一次创建的时候),所以判断如果没有从缓存中获取可重用的cell,那么就创建一个新的cell
if(cell==nil)
{
//创建cell 样式选择UITableViewCellStyleSubtitle的原因是还需要显示图片和详细说明信息reuseIdentifier是为当前创建的cell设置标识,方便下一次的重用
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:ID];
}
//创建cell后为cell赋值
//获取模型数据
WHHero *hero=self.heros[indexPath.row];
//设置图像
cell.imageView.image=[UIImageimageNamed:hero.icon];
//设置文本
cell.textLabel.text=hero.name;
//设置详细说明信息文本
cell.detailTextLabel.text=hero.intro;
//返回创建好的cell
return cell;
}
- 实现cell数据的修改
- 让控件器遵守UITableViewDelegate,设置UITableView的代理为控制器
- 选择cell,通知它的代理选择了某个cell(实现代理方法)
- 在代理方法中创建UIAlertView,获取所选择的cell的文本值(模型数据),填充到UIAlertView的文本框中,同时将当前模型索引存储在消息框的tag属性中,供以后使用。
#pragma mark UITableView的代理方法
//当talbeView中某行数据被选择调用这个代理方法
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath[hw1]
{
//获取被选择行的模型数据
WHHero *hero=self.heros[indexPath.row];
//创建消息框
UIAlertView *alert=[[UIAlertViewalloc] initWithTitle:@"修改操作"message:@"请输入修改后的文本"delegate:selfcancelButtonTitle:@"取消"otherButtonTitles:@"确定",nil];
//设置UIAlertView的代理
alert.delegate=self;
//设置消息框的样式:显示文本框
alert.alertViewStyle=UIAlertViewStylePlainTextInput;
//为文本框赋值当前模型的属性
[alert textFieldAtIndex:0].text=hero.name;
//将当前模型数据和索引值存储到alert的tag属性中,后期需要通过这个索引值找到模型数据进行模型数据的属性值的修改
alert.tag=indexPath.row;
[alert show];
}
- 实现UIAlertView的代理方法,在代理方法中实现业务 :点击确定修改的时候,修改模型数据
- 让当前控制器遵守UIAlertViewDelegate协议
@interface ViewController ()<UITableViewDataSource,UITableViewDelegate,UIAlertViewDelegate>
- 指定UIAlertView的代理为当前控制器
- 实现UIAlertView的代理方法
#pragma mark UIAlertView的代理方法
//点击UIAlertView中的按钮会调用这个方法
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex==1)//点击确定
{
//获取当前模型对象
NSInteger index=alertView.tag;
WHHero *hero=self.heros[index];
//获取当前文本框中的文本值
NSString *str=[alertView textFieldAtIndex:0].text;
//修改模型数据
hero.name=str;
//刷新当前tableView的数据--全部刷新
[self.tableViewreloadData];
}
}
- UITableView数据刷新
- 全部数据刷新:[self.tableView reloadData];消耗更多资源,没有必要
- 只刷新当前被修改的数据行
//只刷新当前被修改的数据行
NSIndexPath *path=[NSIndexPathindexPathForRow:index inSection:0];
[self.tableViewreloadRowsAtIndexPaths:@[path]withRowAnimation:UITableViewRowAnimationBottom];
使用UITableView展示多组汽车品牌数据
界面:
创建模型类:
- 分析plist文件:一个模型对象的某个属性又是一个模型对象(嵌套模型)
- 创建car模型类,声明成员并实现这些成员
@interface WHCar : NSObject
@property (nonatomic,copy)NSString *icon;
@property (nonatomic,copy)NSString *name;
- (instancetype) initWithDic:(NSDictionary *)dic;
+ (instancetype) carWithDic:(NSDictionary *)dic;
+ (NSArray *) cars:(NSArray *)arr;
@end
------------------------------------下面是实现--------------------------------
@implementation WHCar
- (instancetype)initWithDic:(NSDictionary *)dic
{
if(self=[superinit])
{
[self setValuesForKeysWithDictionary:dic];
}
return self;
}
+ (instancetype)carWithDic:(NSDictionary *)dic
{
return [[selfalloc] initWithDic:dic];
}
//通过字典数组转换模型数组
+ (NSArray *)cars:(NSArray *)arr
{
NSMutableArray *desArr=[NSMutableArrayarray];
for (NSDictionary *dicin arr) {
WHCar *car=[WHCarcarWithDic:dic];
[desArr addObject:car];
}
return desArr;
}
- 创建carGroup模型类,声明必要的成员
- cars属性是NSArray数组
- 字典转[hw2] 模型的时候,对carGroup使用KVC得到的还仅仅是数组(字典),还需要对cars数组再进行KVC才能得到最终我们需要的模型数据
- 代码:
@implementation WHCarGroup
+ (instancetype)carGroupWithDic:(NSDictionary *)dic
{
return [[selfalloc] initWithDic:dic];
}
//获取模型对象
- (instancetype)initWithDic:(NSDictionary *)dic
{
if (self=[superinit]) {
self.title=dic[@"title"];
//获取字典数组,再次进行转换为模型数组
self.cars=[WHCarcars:dic[@"cars"]];
}
return self;
}
//返回所有汽车组模型数据
+ (NSArray *)carGroups
{
//读取plist文件,获取字典数组
NSArray *sourceArr=[NSArrayarrayWithContentsOfFile:[[NSBundlemainBundle] pathForResource:@"cars_total.plist"ofType:nil]];
//创建模型数组
NSMutableArray *desArr=[NSMutableArrayarray];
//循环遍历原始字典数组,将字典转模型
for (NSDictionary *dicin sourceArr) {
//字典转模型
WHCarGroup *group=[WHCarGroupcarGroupWithDic:dic];
//将创建好的模型存储到目标数组
[desArr addObject:group];
}
return desArr;
}
@end
-----------------------------下面是实现--------------------------------
@implementation WHCarGroup
+ (instancetype)carGroupWithDic:(NSDictionary *)dic
{
return [[selfalloc] initWithDic:dic];
}
//获取模型对象
- (instancetype)initWithDic:(NSDictionary *)dic
{
if (self=[superinit]) {
self.title=dic[@"title"];
//获取字典数组,再次进行转换为模型数组
self.cars=[WHCarcars:dic[@"cars"]];[hw3]
}
return self;
}
//返回所有汽车组模型数据
+ (NSArray *)carGroups
{
//读取plist文件,获取字典数组
NSArray *sourceArr=[NSArrayarrayWithContentsOfFile:[[NSBundlemainBundle] pathForResource:@"cars_total.plist"ofType:nil]];
//创建模型数组
NSMutableArray *desArr=[NSMutableArrayarray];
//循环遍历原始字典数组,将字典转模型
for (NSDictionary *dicin sourceArr) {
//字典转模型
WHCarGroup *group=[WHCarGroupcarGroupWithDic:dic];
//将创建好的模型存储到目标数组
[desArr addObject:group];
}
return desArr;
}
@end
控制器中定义模型数组,实现懒加载
//数据懒加载
- (NSArray *)carGroups
{
if(_carGroups==nil)
{
_carGroups=[WHCarGroupcarGroups];
}
指定数据源,实现三个数据源方法
//数据源方法:确定组的数量
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.carGroups.count;
}
//确定每一组行的数量
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//获取荆数据
WHCarGroup *group=self.carGroups[section];
return group.cars.count;
}
//创建cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//0.定义标识
NSString *ID=@"car";
//1.从缓存中创建cell
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:ID];
//2.判断缓存中是否有能够重用的cell
if(cell==nil)
{
//如果没有,就重新创建出新的cell
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:ID];
}
//3.获取当前模型数据
WHCarGroup *group=self.carGroups[indexPath.section];
WHCar *car=group.cars[indexPath.row];
//4.为cell赋值
cell.imageView.image=[UIImageimageNamed:car.icon];
cell.textLabel.text=car.name;
//5.返回创建好的Cell
return cell;
}
实现数据源方法,为UITableView设置头部显示文本
//数据源方法:设置头部view的显示文本
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
//获取当前模型数据
WHCarGroup *group=self.carGroups[section];
return group.title;
}
实现数据源方法,添加侧边导航
//数据源方法:设置UITableView右侧显示的组导航
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
//使用循环遍历添加的方式
// NSMutableArray *arr=[NSMutableArray array];
// for (WHCarGroup *group in self.carGroups) {
// [arr addObject:group.title];
// }
//使用kvc
NSArray *arr=[self.carGroupsvalueForKeyPath:@"title"];
return arr;
}
注意事项:
- 要注意什么时候使用数据源方法,什么时候使用代理方法
- 注意UIAlertView的按钮索引值的顺序
- 注意读取全部汽车数据的时候,cars属性还是一个数组,需要再次进行字典转模型的操作。
- cell创建时的优化操作。
常见错误
- 字典转模型没有将cars字典数据转换为模型。
[hw1]处理被用户所选择的cell中的UITableView的代理方法
[hw2]这个案例中最难的部分大概就是这一块了,一定要多理解,反复练习加深体会
[hw3]将获取的字典数组再次进行转换,得到模型数组
UITableView基础
最新推荐文章于 2023-03-11 18:59:12 发布