AutoLayout简介
iOS 开发中一般有两种布局方式:
- Frame + Autoresing布局
- AutoLayout布局
我们知道,在二维坐标系中,元素的位置信息由其在坐标系中原点的X,Y值决定。元素的尺寸信息则由Width,Height值确定。Frame + Autoresizing
布局就是通过设置视图的左上角原点位置(x,y)和尺寸大小(width,height)来设置其布局。再通过Autoresizing
来维持其与父视图的关系。这些关系如下
枚举值 | 效果 |
---|---|
UIViewAutoresizingNone | view的frame不会随superview的改变而改变 |
UIViewAutoresizingFlexibleLeftMargin | 自动调整view与superview左边的距离保证右边距离不变 |
UIViewAutoresizingFlexibleWidth | 自动调整view的宽,保证与superView的左右边距不变 |
UIViewAutoresizingFlexibleRightMargin | 自动调整view与superview右边的距离保证左边距不变 |
UIViewAutoresizingFlexibleTopMargin | 自动调整view与superview顶部的距离保证底部距离不变 |
UIViewAutoresizingFlexibleHeight | 自动调整view的高,保证与superView的顶部和底部距离不变 |
UIViewAutoresizingFlexibleBottomMargin | 自动调整view与superview底部部的距离保证顶部距离不变 |
但是这种布局方式无法处理兄弟视图之间的关系,更无法反向进行布局处理。
Auto Layout dynamically calculates the size and position of all the views in your view hierarchy, based on constraints placed on those views.
在iOS6之后,苹果推出了AutoLayout
。根据苹果的官方文档描述,AutoLayout
会动态的根据我们对视图设置的约束来计算视图的尺寸和位置。AutoLayout
允许程序员用描述性的语言来决定视图之间的布局关系。
// Vertical Constraints
Red.top = 1.0 * Superview.top + 20.0
Superview.bottom = 1.0 * Red.bottom + 20.0
Red.top = 1.0 * Blue.top + 0.0
Red.bottom = 1.0 * Blue.bottom + 0.0
//Horizontal Constraints
Red.leading = 1.0 * Superview.leading + 20.0
Blue.leading = 1.0 * Red.trailing + 8.0
Superview.trailing = 1.0 * Blue.trailing + 20.0
Red.width = 1.0 * Blue.width + 0.0
注意,AutoLayout
的约束设置必须要完整,如果不完整则会产生一些未知错误导致无法完成布局。同时,如果约束设置过多,AutoLayout
就会选择断开其中某些约束,直到能够正确布局。然后再根据未断开的约束来计算布局。同时也可以给约束设置优先级,AutoLayout
就会根据优先级(1-1000)来确定约束的有效性。
AutoLayout工作原理
AutoLayout
之所以能够通过描述性的语言来确定界面布局关系,其核心是Cassowary
算法。AutoLayout会将设置好的描述性语言转化成类似下方的线性不等式来求解,最终利用Frame
来进行布局。
a[1]x[1] + ... + a[n]x[n] = b,
a[1]x[1] + ... + a[n]x[n] <= b, or
a[1]x[1] + ... + a[n]x[n] >= b,
Intrinsic content size
除了用约束来定义界面的位置和尺寸外,一些界面还具有自己的固有尺寸。但是并不是所有的界面都有固有尺寸。
View | Intrinsic content size |
---|---|
UIView and NSView | No intrinsic content size. |
Sliders | <div>Defines only the width (iOS).</div><div>Defines the width, the height, or both—depending on the slider’s type (OS X).</div> |
Labels, buttons, switches, and text fields | Defines both the height and the width. |
Text views and image views | Intrinsic content size can vary. |
什么是固有尺寸呢?如果我们想对一个UILabel
进行布局,如果不使用AutoLayout
,我们需要先设置其文字,然后根据字体、字体大小来计算Size
,然后设置Frame
值。如果利用AutoLayout
进行布局,设置其上、左约束后,Label
就能正常显示。
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Helloooooooooooo,Worlllllllllllllllld!"
label.textColor = .white
label.backgroundColor = .gray
label.font = UIFont.systemFont(ofSize: 14.0)
self.view.addSubview(label)
label.snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(10)
make.top.equalToSuperview().offset(40)
}
}
代码中并没有设置过UILabel
的Size
信息,可是Label
却能够正确显示。这其中必不可少的一个API就是:intrinsicContentSize
方法。这个方法可以获取到界面元素的固有尺寸,而AutoLayout
设置了界面的位置关系。尺寸、位置信息都具备,所以界面就能够正常显示。
intrinsicContentSize
比较有疑问的是:为什么通过
intrinsicContentSize
+AutoLayout
位置约束 = 正确的界面显示?
是AutoLayout
计算好尺寸之后通过intrinsicContentSize
暴露给外界还是AutoLayout
通过intrinsicContentSize
获取已经计算好的界面尺寸来进行布局?
为了验证这个问题,去掉了AutoLayout,转为只设置Label
的Origin
信息,然后通过intrinsicContentSize
设置其大小。
label.backgroundColor = .cyan
label.frame.origin = CGPoint(x: 10, y: 40)
label.frame.size = label.intrinsicContentSize
发现,Label
也能照常显示,那么说明AutoLayout
并不会计算尺寸,而是通过intrinsicContentSize
获取尺寸,然后再加上已经设置好的约束信息,转换为Frame,进行布局。
Content hugging and compression
除此之外,AutoLayout
还会用一对约束来展示界面的intrinsicContentSize
。
其中Content hugging
指可拉伸约束(默认优先级250),Content compression
(默认优先级750)指抗压缩约束,二者都区分水平或者竖直方向。当需要界面保持固有的宽度或高度,可以提升其抗压缩约束优先级;当需要界面能够被压缩或拉伸变化,可以改变其可拉伸约束优先级。
Content hugging
Content hugging
个人理解是指在除开固有内容的情况下,Label
可以被拉伸的力。优先级越高,拉伸力越小。
- 默认情况当
Label1
和Label2
都没有设置抗压缩和可挤压约束,会发现Label1
被拉伸。 - 当提升
Label1
的可拉伸约束优先级的时候,拉伸力变小,Label1
则回到原型,Label2
被拉伸。
- 为了达到同样的目的,我们也可以不改变
Label1
,转至降低Label2
的可拉伸优先级。优先级低,力越大,Label1
则回到原型,Label2
被拉伸。
Content compression
Content compression
个人理解是指在有内容的情况下,内容的抵抗力,优先级越高,抵抗力越强。为了试验,加长了两个Label
的内容长度,默认情况下,Label2
被压缩。
- 当提升
Label2
的抗压缩优先级,其抵抗力增强,Label1
内容被压缩 - 为了达到同样的目的,降低
Label1
的抗压缩优先级,其抵抗力比Label2
小,内容被压缩。
preferredMaxLayoutWidth
和intrinsicContentSize
一起使用的还有preferredMaxLayoutWidth
,官方解释如下:
This property affects the size of the label when layout constraints are applied to it.
这个属性主要用来设置元素的最大宽度(point单位),当利用intrinsicContentSize
根据内容计算尺寸的时候,系统会根据preferredMaxLayoutWidth
预设的最大宽度来计算UILabel
的高度。当这个值没有设置的时候,UILabel
的换行就会以它自己本身的宽度来进行换行适配。如果二者都没有设置,则会根据字体来设置一个默认的高度,并且不会进行换行。如果二者都设置了值,那么高度的计算以preferredMaxLayoutWidth
为准。同时要记得将numberOfLines
设置为想要的行数。
<figure class="half">
<img src="http://op3un88z7.bkt.clouddn.com/2017-09-22-052356.jpg">
<img src="http://op3un88z7.bkt.clouddn.com/2017-09-22-052829.jpg">
</figure>
label.frame.origin = CGPoint(x: 10, y: 40)
label.preferredMaxLayoutWidth = 50
label.numberOfLines = 0
label.frame.size = label.intrinsicContentSize
Autolayout的性能分析
我们知道,AutoLayout
布局计算需要解方程组,就会占用CPU,如果大量的使用AutoLayout进行布局性能消耗和直接使用Frame
布局比起来怎么样?
下面在简单布局和复杂布局的情况下对Autolayout
布局和Frame
布局进行比较。测试机器是iPhone5S,iOS 10.3.3系统。
图中纵坐标为屏幕刷新率,横坐标为布局视图个数。由于视图个数大于200的情况极其少,以至于几乎没有,所以不予考虑。AutoLayout
布局规则为在一个父视图中添加若干个子视图,子视图使用随机数在父视图中进行上、下、左、右约束设置,兄弟视图之间不存在约束关系。Frame
布局规则为在父视图内随机设置Frame进行布局。当视图个数超过20前,简单布局情况下,AutoLayout
布局效率和Frame
布局相差不大,但是在超过20后,AutoLayout
效率成线性降低趋势。
复杂情况布局如上图所示,其横纵左边和简单布局一致。AutoLayout
布局规则为向父视图中添加子视图,同时随机选取其中一个兄弟视图为参照,设置上、下、左、右约束。Frame
布局规则同上,但是为了排除无关变量的影响,添加了随机选取兄弟视图的代码。
上图是复杂布局和简单布局的对比。不难发现,20个视图的时候是一个分水岭。而视图个数在200以内的Frame
布局基本稳定在60FPS。但是结合实际开发,界面元素超过50并且进行布局的个数基本没有,同时50个布局的时候FPS稳定在52-54之间,并不会造成明显卡顿,由于测试机器用的是5S,而6,7,8等系列手机性能更强悍,造成的卡顿理论上会更不明显。所以目前来看,AutoLayout
的性能还是值得信赖的。
博主厉害啊~膜拜