初识
在IOS中最常用的两种布局方式是Frame布局和AutoLayout布局。本文主要讲解Frame布局的相关知识。
Frame布局使用简单,适合初学者,可以采用手写或者xib进行布局,适用于比较简单的界面布局,复杂的布局如果使用Frame布局逻辑会很复杂,不利于布局和后期维护。
Frame的数据结构简单,类型为CGRect,其中original属性定义point,size属性定义大小,初始化成功一个Frame布局之后,可以使用setFrame修改布局。frame的值影响自己的布局,bounds只影响其子view的布局。
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
[self.view addSubview:view];
适用场景
有时候会遇到一些稍微复杂度一点的界面布局,这时候可以使用函数或者对象封装原生的frame布局方法,好的封装让我们可以使用链式布局[^1]的方法,和更简单的方式进行frame布局。
已经有前人实现了frame布局的相对布局[5]
也有三方库支持像使用masory一样使用frame布局[6]和[7]
但是很复杂的界面就不建议使用frame布局了,可以使用autolayout布局,当然还有其它布局方式,这儿不讨论。
Frame布局的效率比AutoLayout高得多,下面是他们的效率比较图表。
图片来源和性能分析更详细可参考[2]
调试
可以使用系统自带的debug view hierachy和第三方的Reveal查看当前界面的布局情况。
Debug view hierachy:
Reveal:
可以在lldb中使用po查看view的frame值,这也是我们调试界面布局的一个重要参考指标,如果不能查看,需要手动在lldb中运行e @import UIKit,然后po就可以了。(如果不清楚po是什么,需要先去看下lldb的基础知识)
Frame动画
可以通过改变frame布局的值来实现一些简单的动画,当然这儿不使用frame是因为
一般外部使用view,使用frame;内部使用或者定义view,使用bounds;动画和旋转一般基于center来进行,很少通过操作frame。
[UIView animateWithDuration:0.5 animations:^{
self.view.center = CGPointMake(20, 20);
} completion:^(BOOL finished) {
//do something
}];
Frame的本质
每个view都有一个frame,这个frame实际是layer的frame,view只是透传frame的值。修改view的frame布局,其实修改的是layer的frame布局。
Frame属性只是一个计算属性,它最后的值由bounds,anchor,position和transform这几个实际属性计算获得,他们之间会相互影响。具体的算法如下,参考文章[3]和参考[4]。
下面是frame计算的伪代码
-(CGRect)frame
{
CGRect retValue = CGRectZero;
if (CGAffineTransformIsIdentity(self.transform))
{ //没有设置仿射变换的情况下
//位置等于中心点的位置减去视图尺寸乘以锚点的值。
retValue.origin.x = self.center.x - self.bounds.size.width * self.layer.anchorPoint.x;
retValue.origin.y = self.center.y - self.bounds.size.height * self.layer.anchorPoint.y;
//尺寸等于视图的尺寸
retValue.size.width = self.bounds.size.width;
retValue.size.height = self.bounds.size.height;
}
else
{
CGAffineTransform left = CGAffineTransformMakeTranslation(-1 * self.bounds.size.width * self.layer.anchorPoint.x, -1 * self.bounds.size.height * self.layer.anchorPoint.y);
//因为下面的坐标变换应用是从(0,0)开始的,因此这里的right指定中心点的位置,也就是下面的复合变换右乘right来实现位置的变换处理。
CGAffineTransform right = CGAffineTransformMakeTranslation(self.center.x, self.center.y);
//整个复合变换是left 左乘 视图的tansform属性然后再右乘right变换。
CGAffineTransform concat = CGAffineTransformConcat(CGAffineTransformConcat(left, self.transform), right);
retValue = CGRectApplyAffineTransform(CGRectMake(0,0,self.bounds.size.width, self.bounds.size.height), concat);
}
return retValue
}
立即刷新Frame
frame布局可以使用setneedlayout设置刷新布局标志位,如果需要立即刷新调用layoutifneed方法,会立即触发调用layoutsubviews,你可以在这个接口中做很多事,比如你可以在这个接口里面刷新它父亲的布局。
@implementation ViewController
-(void)viewDidLayoutSubviews
{
//Second
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView* test = [[UIView alloc] initWithFrame:CGRectMake(0, 44, 20, 20)];
test.backgroundColor = [UIColor blackColor];
[self.view addSubview:test];//First
[self.view layoutIfNeeded];
NSLog(@"3"); //Third
}
@end
从上述代码中的First,Second和Third可以看出执行顺序。
Autoresizingmask
使用Autoresizingmask,系统会自动生成frame布局,并且这个布局在父view变化的时候会跟随变化。
适配
frame布局在处理适配的问题上比autolayout需要做更多工作。frame布局在适配不同的机型上可能会出现很多的魔法变量,当然可以用宏定义解决一部分问题,但是会使得后期难以维护和理解,毕竟代码大部分时间都是用来阅读的,所以可读性还是很重要的。
[1] 链式布局:https://www.jianshu.com/p/67fd5332e7b9
[2] 性能分析:https://draveness.me/layout-performance
[3] Frame计算:https://www.jianshu.com/p/a12cc7356c99
[4] Frame计算:https://www.jianshu.com/p/00482643234d
[5] Frame相对布局:https://github.com/huisedediao/UIView-FrameLayout
[6] Frame相对布局:https://www.jianshu.com/p/d99e94e6b9f9
[7] Frame相对布局:https://www.jianshu.com/p/b76947766583