UITbaleView优化方案
iOS中最常用的控件UITableView,在项目中大量的使用,再次总结一下UITableView性能优化的一些点,也是平时写代码时的需要注意和使用的点。
cell的复用
cell的复用是tableview中最基本的提高性能的使用方法。有两种书写方式。
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *Identifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
//cell=[[[NSBundle mainBundle]loadNibNamed:@“myCell" owner:self options:nil]lastObject];
}
return cell;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *Identifier = @"myCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID forIndexPath:indexPath];
return cell;
}
[self.tableView registerClass:[xxxxCell class] forCellReuseIdentifier:@"myCell"];
//[tableView registerNib:[UINib nibWithNibName:@"xxxxViewCell" bundle:nil] forCellReuseIdentifier:@"myCell"];
这两种方式都可以实现cell的复用。但很多开发者会直接在cellForRowAtIndexPath将model数据直接绑定到cell中。其实调用cellForRowAtIndexPath的时候cell并没有显示。因此为了提高效率,应该吧数据绑定的操作写在tableView:willDisplayCell:forRowAtIndexPath:方法中。但是需要注意的是 willDisplayCell在cell 在tableview展示之前就会调用,此时cell实例已经生成,所以不能更改cell的结构,只能是改动cell上的UI的一些属性(例如label的内容等)。
cell的高度
在开发中一般会有两种cell。一种是定高的cell。采用下面的方式即可设置cell的高度。
self.tableView.rowHeight = 100;
另外一种则是动态高度。有时候一些图片和文本内容的显示会出现cell的动态高度。此时需要计算每一个cell的高度,根据内容的不同去设置每一个cell不同的行高,则通过下面的代理方法来返回cell的高度。
-(CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath:(NSIndexPath*)indexPath{
//根据cell的内容计算高度
//return xxx;
}
每一次cell复用的时候都会重新计算一次,因此为了提高效率,可以将已经计算好的cell的高度保存起来,等再次显示的时候直接取出,节省了计算的时间。
还有通过Autolayout进行布局,使用self-sizing cell的方式。
- 使用Autolayout进行UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)。
- 指定TableView的estimatedRowHeight属性的默认值
- 指定TableView的rowHeight属性为UITableViewAutomaticDimension。
- (void)viewDidload {
self.myTableView.estimatedRowHeight = 44.0;
self.myTableView.rowHeight = UITableViewAutomaticDimension;
}
异步化UI,不要阻塞主线程
像网络图片的加载,使用异步加载。使用SDWebImage或者YYImage等第三方库即可快速实现。将一些耗时操作放入到子线程中进行处理。比如CoreGraphics等进行绘制图表或者一些图形,可以进行异步绘制,然后再回到主线程中显示到UI上。
合理使用hidden。减少clearcolor的使用。
减少视图的数目
我们在cell上添加系统控件的时候,实际上系统都会调用底层的接口进行绘制,大量添加控件时,会消耗很大的资源并且也会影响渲染的性能。当使用默认的UITableViewCell并且在它的ContentView上面添加控件时会相当消耗性能。所以目前最佳的方法还是继承UITableViewCell,并重写drawRect方法。
减少多余的绘制操作
在实现drawRect方法的时候,它的参数rect就是我们需要绘制的区域,在rect范围之外的区域我们不需要进行绘制,否则会消耗相当大的资源。
不要给cell动态添加subview
在初始化cell的时候就将所有需要展示的添加完毕,然后根据需要来设置hide属性显示和隐藏。这样可以有效的减少cell再复用的时候对动态添加的控件进行的操作耗时,提高流畅度。
按需加载
对于快速滑动的tableView,可以考虑只绘制快速滑动即将停止附件的那些cell。对于活动中昙花一现的cell,没必要绘制。不过这可能导致出现空白的cell。
这里先回顾一下uiscrollView的滑动时代理的调用顺序。
1、-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
//开始拖动 UIscrollview 的时候被调用
2、-(void)scrollViewDidScroll:(UIScrollView *)scrollView
//只要contentOffset 发生变化该(拖动、代码设置)方法就会被调用,反过来也可以用于监控 contentOffset 的变化。
3、-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(CGPoint *)targetContentOffset
//方法中 velocity 为 CGPointZero时(结束拖动时两个方向都没有速度),没有初速度,所以也没有减速过程,willBeginDecelerating 和该didEndDecelerating 也就不会被调用如果 velocity 不为 CGPointZero 时,scrollview 会以velocity 为初速度,减速直到 targetContentOffset,也就是说在你手指离开屏幕的那一刻,就计算好了停留在那个位置的坐标
4、-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
//用户结束拖动后被调用,decelerate 为 YES 时,结束拖动后会有减速过程。
5、-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
//减速动画开始前被调用
6、- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
//减速动画结束时被调用,可以用于判断scrollview滑动是否停止。
方法3中从你滑动tableview时,手指离开的一瞬间其实也就已经计算好了要停留的位置。此时你只加载显示停留位置指定的前后几行。这样也能提高效率,但在滑动过程中不可避免的会出现空白cell显示的情况。
//按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。 当 velocity 不为 CGPointZero 时,scroll view 会以 velocity 为初速度,减速直到 targetContentOffset
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
//即将滑动停留后展示的indexPath
NSIndexPath *ip = [self indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
//如果滑动过快,这里是之前的值
NSIndexPath *cip = [[self indexPathsForVisibleRows] firstObject];
NSLog(@"row1 = %zi , row2 = %zi,velocity=%f",ip.row,cip.row,velocity.y);
NSInteger skipCount = 8; //以这个值作为开启滑动区域展示
if (labs(cip.row-ip.row)>skipCount) {
NSArray *temp = [self indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.width, self.height)];
NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
if (velocity.y<0) { //多加载后面3个cell,向下滑动
NSIndexPath *indexPath = [temp lastObject];
if (indexPath.row+3<datas.count) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row+1 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row+2 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row+3 inSection:0]];
}
} else { //多加载上面3个cell
NSIndexPath *indexPath = [temp firstObject];
if (indexPath.row>2) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];
}
if (indexPath.row>1) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];
}
if (indexPath.row>0) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];
}
}
//得到快速滑动即将停止时候需显示的所有cell
[needLoadArr addObjectsFromArray:arr];
}
}
离屏渲染
- 为图层设置遮罩(layer.mask)
- 将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
- 将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
- 为图层设置阴影(layer.shadow *)。
- 为图层设置layer.shouldRasterize=true
- 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层
- 文本(任何种类,包括UILabel,CATextLayer,Core Text等)。
- 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现
圆角的优化
- 使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角
UIImageView imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
//开始对imageView进行画图
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用贝塞尔曲线画出一个圆形图
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
//结束画图
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
- 使用CAShapeLayer和UIBezierPath设置圆角
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImage imageNamed:@"myImg"];
UIBezierPath *maskPath=[UIBezierPath bezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer=[[CAShapeLayer alloc]init];
//设置大小
maskLayer.frame=imageView.bounds;
//设置图形样子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.view addSubview:imageView];
shadow优化
对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能。示例如下:
imageView.layer.shadowColor=[UIColor grayColor].CGColor;
imageView.layer.shadowOpacity=1.0;
imageView.layer.shadowRadius=2.0;
UIBezierPath *path=[UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath=path.CGPath;