由于UITableView是日常开发中使用频率相当高的一个视图控件,我们对它用户体验的关注度相比其他视图要高很多。一个顺滑的TableView值得花更多的时间去打磨。
造成卡顿的主要原因
- Cell上各个子控件的布局计算
- Label等控件文本内容的宽高计算
- 文本渲染
- 图形绘制
- 对象的创建与销毁等
优化方案
-
能用frame布局最好, 但现实情况一般是AutoLayout+cell高度计算
1> 我们可以选择
纯代码
与storyboard(or xib)
两种方式进行自动布局
自动布局.png2> 创建一个Layout类,包含cell高度, 必要控件的高度等以及它的构造函数(传入数据源Model)
@class MyModel; @interface MyLayout : NSObject @property (nonatomic, assign) CGFloat height; //cell高度 @property (nonatomic, assign) CGFloat messageH; //message控件高度 - (instancetype)initWithMyModel:(MyModel *)myModel; //构造方法
@end
3> 在获取model的同时创建对应的MyLayout对象,Model-LayoutObject-Cell一一对应。
NSMutableArray *temp = [NSMutableArray new];
NSMutableArray *temp1 = [NSMutableArray new];
for (NSDictionary *dict in array) {
MyModel *model = [[MyModel alloc] initWithDictionary:dict];
MyLayout *layout = [[MyLayout alloc] initWithMyModel:model];
[temp addObject:model];
[temp1 addObject:layout];
}
self.dataArray = temp.copy;
self.layouts = temp1.copy;
```
3> 通过提取model中的数据确定各个view控件的高度,并提前定义好各个间距的值,通过控件高度与间隔高度求和的方式得到最终的cell高度
- (void)layoutWithMyModel:(MyModel *)model {
[self layoutMessage:model.message];
CGFloat height = 0;
height += kTopSpacing;
height += self.messageH;
height += kSpacing;
height += kDefaultTimeHeight;
height += kSpacing;
height += kLineHeight;
self.height = height;
}
4> 实现UITableView的代理,为cell的高度赋值
#pragma mark - UITableView Delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
MyLayout *layout = self.layouts[indexPath.row];
return layout.height;
}
思考:创建并将layoutObject存储在self.layouts中,有效的避免了实现动态布局时重复计算cell高度而消耗更多的性能。将这个操作放在后台线程中执行应该对性能的提升更大。
- 复杂的文本可以考虑使用CoreText或者CATextLayer进行绘制,YYKit的作者更是提出了异步绘制的方案
1> CoreText的基本使用参照唐巧前辈的博客
2> CATextLayer的基本使用,可以参考这个
- 关于View的圆角、阴影、边框等渲染工作, 尽量不要使用layer的cornerRadius、shadow、border等属性, 避免。
1> 对于图片的圆角操作, 我们可以在获取到网络数据的同时将图片渲染成圆形。
2> 对于其他View,我们可以使用以下代码:view.layer.shouldRasterize = YES; view.layer.rasterizationScale = [UIScreen mainScreen].scale;
-
尽量减少视图的层级, 能用Layer替代的就不创建新的View
比如下图的印章效果,我们可以通过ElipseLayer画圆圈、CATextLayer绘制文本轻松实现,避免了创建不必要的View
image.png
- 使用SDWebImage或者YYWebImage异步加载图片
- 使用异步UI库(学习成本太高)
- 封装TableView的DataSource(除了减少ViewController的代码好像对性能影响不大)
1> 创建一个DataSource类,包含回调block,数据源数组,构造函数等:
2> 让这个类遵守UITableViewDataSource协议,并实现以下代理方法:// MyTableViewDataSource.h typedef void (^CellconfigureBlock)(id cell ,id item); @interface MyTableViewDataSource : NSObject <UITableViewDataSource> @property (nonatomic, strong) NSArray *dataArray; //数据源 - (instancetype)initWithDataArray:(NSArray *)dataArray cellReuseIdentifier:(NSString *)reuseIdentifier cellConfigureBlock:(CellconfigureBlock)configureBlock; - (id)itemAtIndexPath:(NSIndexPath *)indexPath; //得到对应indexPath的Model @end
#pragma mark - UITableView DataSource - (NSInteger) tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataArray.count;
}
-
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
id cell = [tableView dequeueReusableCellWithIdentifier:self.cellReuseIdentifier forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
self.configureBlock(cell, item);return cell;
}
3> 在ViewController中创建并使用这个类的实例
self.dataSource = [[MyTableViewDataSource alloc] initWithDataArray:self.dataArray cellReuseIdentifier:kMyCellReuseIdentifier cellConfigureBlock:^(MyTableViewCell *cell, MyModel *item) {
cell.model = item;
}];
self.tableView.dataSource = self.dataSource;
#####PS:以上几点其实都是基于功能模块基本实现并且没有什么问题的情况下做出的优化 ,若你的代码还存在一些基本的问题, 这些优化可能帮不上你什么忙。举个列子:
* 阻塞主线程
#####这种情况UI卡住你就得找找你写的BUG了。
***
####参考资料:
[iOS
***