继续上一篇,本周的重构重点是UI部分代码的优化。
1. AutoLayout及Masonry
AutoLayout是苹果在IOS6中推出的一种新的UI构建方式,旨在解决不同屏幕分辨率之间的适配问题。相信大多数人可能跟我一样,对这种方式是又爱又恨,因为AutoLayout中的确存在很多坑。不过随着iOS设备尺寸越来越多,还是值得去学习掌握AudoLayout的。
本次重构中在UITableViewCell中使用AutoLayout上遇到了一个坑,正常情况下在cell中使用AutoLayout需设置约束上下左右都为-8才能铺满整个Cell。但发现在iOS6中没有问题,但在iOS7以上,左右约束需设置为-15才能铺满。我的解决方案是在cell.contentView上再添加一层父View,针对不同的系统做了一个适配。但问题的根本原因目前还没有找到,有待后续观察。
另外,对于手写约束来说,使用苹果原生的API可能会很痛苦,因为约束代码将又臭又长,例如:
[self.view addConstraint: [NSLayoutConstraint constraintWithItem:AView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:BView
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];
仅表示AView的左侧距离BView的左侧一个单位,所以有必要引入一些第三方工具。
Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布局 简洁明了 并具有高可读性 而且同时支持 iOS 和 Max OS X。
上面那句使用Mansory可以精简为:
[AView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(BView.left).with.offset(1);
}];
一个View的多个约束可以在同一个Block中实现,并且代码书写方式让人更容易理解。
更多使用技巧请戳:Masonry
2. Cell中对Layer的处理
其实cell中应避免一切对Layer的处理,包括圆角,阴影,甚至不应该包含任何透明View,因为这种渲染对系统的开销非常大,众多的Cell将使页面变的非常卡,在使用Layer时,也应该使用如下的方法减少以系统的开销。
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
CGPathRef path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
[self.layer setShadowPath:path];
3. UITableViewCell中嵌套UITableView
看到网上有人说应该避免在UITableViewCell中使用UITableView,我觉得可以视需求的不同做不同的处理。对于一个模型结构非常复杂的TabeView,嵌套TableView可以降低代码的耦合,将不同的业务模型分散处理。只是需要注意的是,子TableView和父TableView的实现不应该在同一个文件中处理,也就是说delegate和dateSource不应该指向同一个对象,可以将子TableView封装成一个Cell,delegate和dataSource都交由这个Cell处理,这样才能有效降低代码的耦合,并且精简原文件的逻辑和大小。
4. UITableView中间层模型的封装
相信很多人会Cell的展示逻辑直接放到TableView的delegate中处理,例如:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (A) {
if(B){
return BHeight;
}
return AHeight;
}else if (C) {
if(B){
return BHeight;
}
return CHeight;
}
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(A){
if(B) {
return 2;
}
return 1;
}else if(C) {
if(B){
return 2;
}
return 1;
}
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(A) {
if(B) {
return BCell;
}
return ACell;
}else if(C) {
if(B){
return BCell;
}
return CCell;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(A) {
if(B) {
doB();
}
doA();
}else if(C) {
if(B){
doB();
}
doC();
}
}
所有代理中的逻辑都必须相同,而且同样的方法要写多次,例如上面的B。使用这样的方式,当遇到逻辑非常复杂的TableView时将使我们苦不堪言。TableView的代理应该只负责去构建Cell,而不应该来处理逻辑判断。所以,我们应该构建一个中间的模型层,在TableView reloadData的时候加载这个模型层,例如:
- (void)setupTableModel
{
if(A) {
if(B) {
[arrayModel addObject:BModel];
}
[arrayModel addObject:AModel];
}else if(C) {
if(B){
[arrayModel addObject:BModel];
}
[arrayModel addObject:CModel];
}
}
- (void)reloadTable
{
[self setupTableModel];
[tableView reloadData];
}
此时:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[array objectAtIndex:row] isEqual:AModel]) {
return AHeight;
}else if ([[array objectAtIndex:row] isEqual:BModel]) {
return BHeight;
}else if ([[array objectAtIndex:row] isEqual:CModel]) {
return CHeight;
}
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return arrayModel.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[array objectAtIndex:row] isEqual:AModel]) {
return ACell;
}else if ([[array objectAtIndex:row] isEqual:BModel]) {
return BCell;
}else if ([[array objectAtIndex:row] isEqual:CModel]) {
return CCell;
}
return nil;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[array objectAtIndex:row] isEqual:AModel]) {
doA();
}else if ([[array objectAtIndex:row] isEqual:BModel]) {
doA();
}else if ([[array objectAtIndex:row] isEqual:CModel]) {
doA();
}
}
此时的delegate中只关注tableView的Cell构建和Cell行为,并不关心任何构建顺序等逻辑判断。
网友评论
if (iOSVersion > 8.0)
{
self.preservesSuperviewLayoutMargins = NO;
}
另外,关于苹果推荐的距离层次边缘距离的问题有这么几个地方:
1.在Xcode 6.2(好像是这个版本,没太注意),至少,苹果默认推荐一个距离边缘距离8,默认是开启的,在之后就开始更改了,改为16(好像是16),这个和编译器解析xib有关,和手机版本无关。
2.苹果建议的页边距只有ib中有该选项,代码设置未看到该设置。如果需要关闭,则在设置约束距离上下左右距离时候关闭那个上下左右框下方的选项,英语不是很好,没注意叫什么名字,莫笑莫笑~~
另外,关于前文提出的约束卡顿问题我有以下不同意的地方:
1.使用约束卡顿并不再计算frame,而是设置约束卡顿。表现形式为:设置约束一旦展开cell,则不更新此cell计算的高度则会缓存,并不存在滚动计算高度的情况,所以并不会造成卡顿。另外在进行复用时候则会对刚才消失的约束执行一系列操作来产生二级回复列表,此时因为已经缓存高度,所以不会调用高度计算函数。
2.约束的设置如果在生成时候设置复杂的约束,重用时候并不会造成卡顿(没测试,因为看cpu 使用率,和其他的没有什么区别…)。当cpu使用率超过20则会能感觉到卡顿。
我写的步骤如下:
1.重用cell时候先移除里面的二级回复view
2.读取数据列表数组,取出对应个数的缓存的二级回复view,统一设置约束
3.使用fd优化
结果:卡顿,随着二级回复的增多卡顿越长,测试为,八个二级回复列表卡顿0.14秒。
现用优化方案:改为frame进行布局一样的逻辑,无卡顿…
备注:约束使用xib或者Masonry 均卡顿较大。