标签(空格分隔): UITableView UITableViewCell UITableView性能优化 UITableView性能优化精讲
摘要:UITableView是iOS开发中非常重要的控件之一,它能够展示多行数据,支持滚动.在大部分APP中都占有很大的比重.那么有关UITableView的性能优化也显得尤为重要,本文后面也将着重讲这个。
##UITableView的简单使用 ###1、UITableView的创建
代码方式
//tableView的创建必须制定一个样式,样式在初始化之后不能修改
//tableView分两种风格,Plain:不分区,grouped:分区
_tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
//告诉tableView,它上面的cell是根据UItableViewCell创建的,如果重用的cell找不到,系统会根据这个类来创建一个cell,不需要编程人员创建
//解释2://给tableView的某个重用标示符注册一个类,当tableView找这个重用标示符的cell,如果找不到,就会自动创建这个类的对象,(始终能找到可重用的cell)
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
[self.view addSubview:_tableView];
xib方式 在设计界面拖入,并在属性检查器中设置各种属性
###2、UITableView的属性
// 设置每一行cell的高度
self.tableView.rowHeight = 80;
// 设置每一组的头部标题高度
self.tableView.sectionHeaderHeight = 50;
// 设置每一组的尾部标题高度
self.tableView.sectionFooterHeight = 50;
// 设置分割线的颜色
self.tableView.separatorColor = [UIColor redColor];
// 设置分割线的样式
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
// 设置表头控件---这里主要应用是打广告
self.tableView.tableHeaderView = [[UISwitch alloc] init];
// 设置表尾控件---这里主要应用是加载数据
self.tableView.tableFooterView = [[UISwitch alloc] init];
// 设置索引条的文字颜色
self.tableView.sectionIndexColor = [UIColor orangeColor];
// 设置索引条的背景颜色
self.tableView.sectionIndexBackgroundColor = [UIColor yellowColor];
###3、隐藏TableView分割线的方法
// 方法一:设置分割线的颜色
self.tableView.separatorColor = [UIColor clearColor];
// 方法二:设置分割线的样式
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
###4、UITableViewCell的属性
//cell中默认包含的空间
cell.imageView
cell.textLabel
cell.detaliTextLabel
cell.contentView
// 设置右边显示的指示控件
// accessoryView的优先级 > accessoryType
cell.accessoryView = [[UISwitch alloc] init];
// 设置右边显示的指示样式
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 设置cell选中的样式
// 设置cell的选中样式无
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// 下面三种方式在iOS7之后,表现形式一样了,都是灰色
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
###5、设置cell的背景颜色
// 总体效果为大背景蓝色,字体背景为红色,下面代码可以调换顺序,效果一样
cell.backgroundColor = [UIColor redColor];
UIView *bg = [[UIView alloc] init];
bg.backgroundColor = [UIColor blueColor];
cell.backgroundView = bg;
###6、设置cell选中的背景View
// 设置cell选中的背景view
UIView *seletedBg = [[UIView alloc] init];
seletedBg.backgroundColor = [UIColor yellowColor];
cell.selectedBackgroundView = seletedBg;
###7、两个协议
tablewView代理方法的执行顺序。 UITableView返回多少组----->每组返回多少行cell--->计算每个cell的高度---->指定cell布局
UITableViewDelegate
/**
* 1.当用户点击(选中)某一行cell就会调用这个方法
*/
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"选中了:%zd",indexPath.row);
}
/**
* 2.当用户取消点击(选中)某一行cell就会调用这个方法
*/
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"取消选中了:%zd",indexPath.row);
}
/**
* 3.返回每一组的头部高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
}
/**
* 4.返回每一组的尾部高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
}
/**
* 5.返回每一行cell的高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row % 2 == 0) {
return 50;
} else {
return 100;
}
}
/**
* 6.返回每一组的头部控件
*/
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return [UIButton buttonWithType:UIButtonTypeContactAdd];
}
/**
* 7.返回每一组的尾部控件
*/
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
return [UIButton buttonWithType:UIButtonTypeContactAdd];
}
/*
* 8.设置每一行的编辑样式:当表处于编辑状态时,默认的编辑样式为删除
*/
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
//编辑状态为删除
return UITableViewCellEditingStyleDelete;
//编辑状态为插入
//return UITableViewCellEditingStyleInsert;
}
//通过edditing的值来显示编辑状态
_tableView.editing = !_tableView.editing;
/*
* 9.表提交编辑状态的时候会调用这个方法
*/
-(void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
[_dataArray removeObjectAtIndex:indexPath.row];
[_tableView reloadData];
}
/*
* 10.设置编辑中删除的文字
*/
-(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
{
return @"走你";
}
/*
* 11.右侧按钮被点击时会调用该方法
*/
-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"调用了cell右侧按钮的方法");
}
###8、两个协议二:UITableViewDataSources
/**
* 告诉tableView一共有多少组数据
*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
/**
* 告诉tableView第section组有多少行
*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
/**
* 告诉tableView每一行显示的内容(tableView每一行的内容是是第一个UITableViewCell)
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
/**
* 告诉tableView每一组的头部标题
*/
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
/**
* 告诉tableView每一组的尾部标题
*/
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
/**
* 返回索引条的文字
*/
- (NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
// NSMutableArray *titles = [NSMutableArray array];
// for (XMGCarGroup *group in self.carGoups) {
// [titles addObject:group.title];
// }
// return titles;
// 抽取self.carGoups这个数组中每一个元素(XMGCarGroup对象)的title属性的值,放在一个新的数组中返回
return [self.carGoups valueForKeyPath:@"title"];
##二、UITableViewCell 在UITableView中,每一个单元格被称为cell,如果想在UITableView中显示数据,需要设置UITableView中cell的数量及每个cell显示的内容.UITableView并不能直接显示数据,它需要设置数据源(datasource),数据源遵守协议,并实现其中对应的方法设置数据及内容即可. ###UITableViewCell的创建
-
设置Cell的三个属性
cell.imageView //cell左边的图片 cell.textLabel cell.detailTextLabel
-
通过自定义类 创建一个累继承于UITableViewCell,添加如下属性
@property (nonatomic,retain)UIImageView *headerImageView; @property (nonatomic,retain)UILabel *nameLabel; @property (nonatomic,retain)UILabel *timeLabel; @property (nonatomic,retain)UILabel *messageLabel; @property (nonatomic,retain)UIImageView *picImageView;
.m文件中的实现
//如果是alloc创建的cell,各个自定义属性空间的初始化代码写在init方法中 (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { //头像 _headerImageView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 8, 60, 60)]; _headerImageView.layer.cornerRadius = 30; _headerImageView.layer.masksToBounds = YES; [self.contentView addSubview:_headerImageView]; //名字 _nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(76, 8, 240, 20)]; _nameLabel.font = [UIFont boldSystemFontOfSize:16]; _nameLabel.textColor = [UIColor redColor]; [self.contentView addSubview:_nameLabel]; //发布时间 _timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(76, 36, 240, 20)]; _timeLabel.font = [UIFont systemFontOfSize:12]; [self.contentView addSubview:_timeLabel]; //动态正文 _messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(8, 76, 304, 60)]; _messageLabel.font = [UIFont systemFontOfSize:16]; //numberOfLines行数,设置为0不限制行数。 _messageLabel.numberOfLines = 0; [self.contentView addSubview:_messageLabel]; //动态配图 _picImageView = [[UIImageView alloc] initWithFrame:CGRectMake(8, 144, 100,100)]; [self.contentView addSubview:_picImageView]; } return self; }
-
通过xib自定义 首先创建一个带xib的UITableViewCell,拖控件设置cell高度。拉线生成属性;
自定义类中
//如果cell是从xib中加载的,那么不走init方法,所以初始化代码不能写在init方法中
//当自身从xib中加载的时候调用,所以自定义xib的代码应该在这里实现
(void)awakeFromNib {
[super awakeFromNib];
// Initialization code
_headerImageView.layer.cornerRadius = 30;
_headerImageView.layer.masksToBounds = YES;
}
注册重用
```
//给TableView的某个重用标示符注册一个xib,当tableView找这个标识符时,如果找不 到,就会自动从注册的西边中区加载一个cell。
[_tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:nil] forCellReuseIdentifier:@"cell"];
```
##三、性能优化
1)尽量使用cell的复用。
使用cell的复用,可以减少内存的开销,没有开辟新的空间,也减少了一些计算量。
复用原理:当滚动列表时(UITableView)部分cell会移除Window 但是移除的cell并没有被立即释放 而是放到了一个叫做复用池的对象池中,处于待定状态,当有新的cell要出现在Window界面上时,首先会从复用池中寻找是否有相同类型的cell,如果有直接拿过用(最直观的表现是新出现的cell有没有开辟新的内存空间),如果没有,创建一个新的类型的cell,所以UITableView可能拥有多种类型的cell,复用池也可能存储着多种类型的cell,系统通过定义reuseIndentifer作为每个cell的唯一标示符来确定即将出现的cell复用何种类型的cell。
2)对于不定高的cell 提前将每个cell的高度存入数组,出现一个cell的时候,直接从数组中拿出确切的高度即可,不用临时计算cell的高度。对于固定高的cell和不定高的cell同样适用。同样也可以在存储在model中,在获取数据后要赋值给model时进行计算。
3)涉及网络请求加载数据在UITableView滑动结束的时候在进行加载数据(渲染)避免卡顿。
1、UITableView继承自UIScrollView,继承了后者的方法。
//滑动结束的方法
- (void)scrollViewDidEndDragging:(UIScrollView*)scrollView willDecelerate:(BOOL)decelerate
//减速结束之后的方法
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView
2、在tableView必须实现的二个方法中(加载cell的方法中)将数据的渲染写在以下if语句中
if(self.tableView.dragging==NO&&self.tableView.decelerating==NO)
4)对于tableView的自定义控件 尤其是UIImageView,尽量减少使用圆角,阴影等layer属性,尽量减少使用alpha(透明度)来设置透明度,(在项目开发中,让UI设计师设计原图就是带圆角的图) 阴影,圆角这些layer效果都是在GPU中完成的。
1、当多个视图重叠时,一个像素同时属于很多subviews,GPU会进行合成渲染,需要渲染一个像素两次或多次,而渲染的最慢的操作就是混合。因此当视图结构太过复杂,就会大量消耗GPU的资源。所以当一个空间本身是不透明,注意设定alpha为1,这样可以避免无用的alpha通道合成,降低GPU的负载。 另外在layer层渲染图层时会涉及上下文切换以及离屏渲染之类的,系统开销会很大,特别是在cell视图很复杂的时候,由于渲染问题导致的内存开销会让你的tableview非常卡顿。比如cell中需要设置头像圆形直接设置圆角会很卡,那么我们可以用Quartz2d把拿到的图片处理一遍在给cell使用。
2、对控件设置cornerRadius后对其进行clip或mask操作时 会导致offscreenrendering这个也是在GPU中进行的 如果在滑动时 圆角对象太多 回到GPU的负载大增幅。
这时我们可以设置layer的shouldRasterize属性为YES,可以将负载转移给CPU,更彻底的是直接使用带圆角的原图。
5)尽量使用懒加载
懒加载又称为延迟加载,实际上是重写某个对象的getter方法 原理:程序一开始并不对它进行初始化 而是在用到他的时候 才为他开辟内存供它使用。
好处:
1、不必将创建的对象的代码全部写在ViewDidLoad中,代码可读性强。 2、每个控件的getter方法,分别负责各自的实例化处理,代码彼此之间独立性强 松耦合。
6)减少返回给的cell里面的处理逻辑和处理时间。
以驴妈妈为例:各个UI控件整合到一起,实际上只有一个控件。
7)设置每个cell的opaque属性 ----面试亮点
opaque意思是不透明的 浑浊的 有YES和NO二个结果,如果控件本身不透明,我们设置opaque为YES。
opaque为YES表示告诉iOS当前视图背后没有需要绘制的内容,同时允许iOS的绘图方法通过一些优化来加速当前视图的绘制。
为什么我们设置Alpha的值为1的时候仍然要设置opaque的属性为YES? Alpha属性只对当前需要绘制的视图起作用,如果当前视图并没有填满父视图或者当前视图上存在一些洞(由Alpha通道所致),那么图像视图下方的内容将仍然可见,不管Alpha的值是多少。选中就是让iOS明白不需要为图像视图之后的东西浪费绘制时间。
以下是官方描述
default is YES. opaque views must fill their entire bounds or the results are undefined. the active CGContext in drawRect: will not have been cleared and may have non-zeroed pixels
8)分段加载数据
设置分页加载数据,也就是上拉刷新和下拉加载。
以下是cell简单复用代码
#import "ViewController.h"
#import "XMGTestCell.h"
@interface ViewController ()
@end
@implementation ViewController
NSString *ID = @"wine";
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = 100;
// 注册 ID这个标识 对应的 cell类型 为UITableViewCell这种类型
[self.tableView registerClass:[XMGTestCell class] forCellReuseIdentifier:ID];
}
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 200;
}
/**
* 每当一个cell进入视野范围内就会调用1次
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.去缓存池取可循环利用的cell
XMGTestCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 2.设置数据
cell.textLabel.text = [NSString stringWithFormat:@"第%zd行数据",indexPath.row];
return cell;
}