iOS-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的方式。

  1. 使用Autolayout进行UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)。
  2. 指定TableView的estimatedRowHeight属性的默认值
  3. 指定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 :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现

圆角的优化

  1. 使用贝塞尔曲线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];
  1. 使用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;

 

人已赞赏
iOS文章

iOS CoreMotion的使用

2019-10-13 17:28:54

iOS文章

iOS11无线连接手机真机测试

2019-10-13 18:39:25

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索