自动布局Auto Layout
Auto Layout,通过设置在View上的约束,动态计算视图层次结构中所有的View的尺寸和位置。举个栗子,你约束一个Button,令它的水平中心线和一个ImageView相同,并且它的上边缘距离ImageView的下边缘有8个像素。如果ImageView的尺寸或者位置改变,Button会自动调整,以符合之前设置的约束。
基于约束的Auto Layout,使我们搭建能够动态响应内部和外部变化的用户界面。
外部变化
外部变化发生于superview的尺寸或者位置改变,比如,
-
设备屏幕旋转;
-
支持不同屏幕大小的设备。
这时,所有的View都要重新计算尺寸和位置。每一次变化,都会刷新视图层级结构的布局。这些变化大部分发生在运行时,它们需要APP能够动态响应。
内部变化
内部变化发生于你的界面中的View的尺寸或者位置发生改变。比如,
-
APP中显示的内容的改变,新的内容可能需要一个新的布局。一般,在显示文字或者图片时会出现这种情况;
-
APP支持动态设置。如果用户可以设置字体大小,这将会改变任何与文本相关的控件的高度或者宽度,布局必须能够适应变化。
约束Constraints
Auto Layout的实现是基于设置在View上的一系列约束的。每一条约束都是一个表达式。
下图是官方文档给的示例图:
这个约束表明,Red View
的左边缘
与Blud View
的右边缘
的距离为8。这个等式由以下几个部分组成:
-
Item 1 :表达式中的第一个控件。在这个例子中是
Red View
; -
Attribute 1 :第一个控件的一个属性。在这个例子中是
Red View
的leading edge
; -
Relationship :表达式左右两边的关系,可以使
等于
,大于等于
或者小于等于
。在这个例子中,两边的关系是相等的; -
Multiplier :和第二个控件的属性相乘的乘数,是一个浮点型。在这个例子中是
1.0
。一般情况下,这个值不可置为0.0
; -
Item 2 :表达式中的第二个控件。在这个例子中是
Blue View
。它是可以为空的,即表达式变成Item 1 * Attribute 1 = 0.0 * NotAnAttribute + Constant
; -
Attribute 2 :第二个控件的一个属性。在这个例子中是
Blue View
的trailing edge
; -
Constant :一个浮点型的常数。在这个例子中是
8.0
。
大部分的约束是定义两个控件之间的关系。这些控件必须是View
或者是Layout Guide
。约束也可以定义一个控件的两个属性之间的关系,比如设置一个控件的上边缘到下边缘的距离、左边缘到右边缘的距离,即它的高度或者宽度。当表达式中的Item 2
为空时,它的属性必须被设为Not An Attribute
,并且Multiplier
置为0.0
。
约束中用到的属性
通常情况下,包含四个边(leading,trailing,top和bottom),以及高度(height),宽度(width),水平中心点(CenterY),垂直中心点(CenterX)。文本类型的控件还有一个基线(baseline)属性。
属性说明
-
Height和Width。这两个属性可以被直接赋值,可以是一个常数,也可以是其他View的Height或者Width值。但是,不可以为负数。
-
Top、Bottom、Baseline。可以和
Top
、Bottom
、Baseline
、CenterY
组合。 -
Leading和Trailing。可以和
Leading
、Trailing
、CenterX
组合。 -
Left和Right。避免使用这两个属性,而使用
Leading
和Trailing
来替代它们。 -
CenterX和CenterY。CenterX可以和
Leading
、Trailing
、Left
、Right
组合。CenterY可以和Top
、Bottom
、Baseline
组合。
使用属性定义约束
上面提到的属性可以分为两类,尺寸相关
和位置相关
。尺寸相关(如height、width)用来定义物件的大小。位置相关(如leading,top)的属性用来表明该物件和其他物件之间的位置关系。使用这些属性时需要注意:
-
不要使用尺寸相关的属性去约束一个位置相关的属性。
-
只可以给尺寸相关的属性直接赋值一个常量。
举几个简单的例子:
// 设置一个固定高度
View.height = 0.0 * NotAnAttribute + 40.0
// 设置两个按钮之间的固定距离
Button_2.leading = 1.0 * Button_1.trailing + 8.0
// 让两个按钮的左边缘对齐
Button_1.leading = 1.0 * Button_2.leading + 0.0
// 给两个按钮相同的宽度
Button_1.width = 1.0 * Button_2.width + 0.0
// 让View的中心和父类的中心相同
View.centerX = 1.0 * Superview.centerX + 0.0
View.centerY = 1.0 * Superview.centerY + 0.0
// 设置一个View的宽高比
View.height = 2.0 * View.width + 0.0
约束的设置没有最好的,只有最适合的。
约束的优先级
优先级priority
是Auto Layout在计算的时候用到的参数。优先级的值可以是1-1000任意整数。系统定义了low(250)、medium(500)、high(750)和required(1000)四个等级。一般情况下,我们手动设置的优先级的值也会集中在这四个等级下。
优先级默认值是1000。
关于Auto Layout是如何通过优先级来计算出解决方案,我在看完官方文档后还是一头雾水。希望有大神可以指点一二。
使用约束
添加约束
在storyboard中有3种方式添加约束。
-
在View之间使用
Control-Drag
; -
使用
Pin
和Align
工具; -
让Interface Builder自动添加约束。
Control-Dragging
所谓Control-Draging
就是按住Control
键,用鼠标左键拖动
的方式添加约束。这两步操作也可以用按住鼠标右键拖动
来替代。
当释放鼠标左键后,就会弹出一个HUD,显示可以设置的约束。
Interface Builder会根据选择的两个控件以及拖动的方向筛选出可以设置的约束。如果拖动的方向倾向于水平,你可以选择设置水平方向上的间距和垂直方向上的对齐方式。反之,如果拖动的方向倾向与垂直,则可以选择设置垂直方向上的间距和水平方向上的对齐方式。
提示:
-
可以从一个控件拖动到另一个控件,设置它们之间的关系。也可以拖动到控件自身,设置宽度和高度;
-
不仅可以在Scene中直接拖动,可以在Storyboard左侧的视图大纲中用同样的方式拖动。在大纲中拖动设置约束,会显示出所有的可选约束,而不会进行筛选;
-
Control-Dragging可以非常快速得设置约束。这些约束是基于Scene中View的当前的位置,因此在设置约束之前要定位好View。
使用Stack、Align、Pin、Resolve工具
Interface Builder在Storyboard的编辑窗口的右下角提供四个自动布局的工具,分别是Stack
、Align
、Pin
、Resolve Auto Layout Issues
。
当你想精确控制约束的Constant或者想一次性添加多个约束,可以使用Align和Pin工具。使用Align和Pin还有一个好处,我们不是必须要设置好View的位置,而是只需要定好相对位置,添加约束,然后update frames
。Auto Layout会自动计算出正确的位置。
Stack Tools
Stack Tools可以将选中的一个或者多个控件嵌入到一个Stack View中,并会重新计算布局。Stack View是iOS 9添加的新特性。
对于Stack View,我还没有弄明白使用方法,所以这里不讲述。
Align Tool
Align Tools可以快速对齐控件。选择一个或多个你想对齐的控件,然后单击Align Tool
。然后会弹出可选的一系列对齐方式。
选择其中的选项,然后点击Add Constraints
。之后,就会自动添加对齐的约束设置。大部分情况下,会选择两个或者两个以上的View来设置对齐。Horizonally in Container
和Vertically in Container
这两个可以添加到单一的View上。
Pin Tool
Pin是大头针的意思。所以这个工具可以用来给View定位。它可以让我们快速设置一个View相对于它周边View位置或者它的宽高。选择一个你想对其进行定位的View,单击Pin Tool
,会弹出如下的窗口。
窗口的上半部分,可以设置选中的View的Top,Bottom,Leading,Trailing与相邻最近的View的间距。最初显示的数字是当前的间距。我们可以输入一个自定义的值,还可以点击输入框右边的倒三角,在弹出的下拉菜单中选择参照的View。关于Constrain to margins
选项,如果选中,会将父视图的外边距作为间距的值的参考。
下半部分可以设置宽高相关的属性。宽和高默认的是Scene中的尺寸,也可以自定义值。宽高比的默认值也是根据Scene中的尺寸进行计算。如果想自定义的话,只有在设置完宽高比之后修改这个约束。
一般情况下,选择一个View,对它进行定位。选择两个及其以上的View设置Equal Height
或者Equal Width
。使用Pin Tool设置完约束后,可能需要Update frames
。
Resolve Auto Layout Issues
Resolve Auto Layout Issues提供一些解决Auto Layout问题的方法。上半部分只针对选中的View,下半部分则针对Scene中所有的View。
我们可以
-
根据当前约束更改frame;
-
根据当前frame更改约束的设置;
-
添加缺少的约束;
-
清除已添加的约束;
-
设置系统推荐的约束。
这些功能字面上写得很清楚,具体的效果大家可以用简单的Demo来看一下。
让Interface Builder为我们设置约束
Interface Builder可以为我们创建部分或者全部的约束。根据所提供的View的尺寸和位置,它会推断出最好的约束。前提是,我们必须确定View的位置并不再更改。一个小小的间距的改变,可能对于整个布局来说确实巨大的。
如果想让Interface Builder来完成约束的添加,单击上文提到的Resolve Auoto Layout Issues
工具,点击Reset to Suggested Constraints
。Interface Builder就会为已选的(也可以是Scene中全部的)View创建合适的约束。
另外,我们可以自己添加一部分约束,然后选择Add missing Constraints
,让Interface Builder来添加剩下的所需的约束。
这种方法可以快速完成约束的设置。但是,有可能运行得到的UI并不是你想要的。要不断地测试UI,修改约束,以达到最终想要的效果。
编辑约束
添加约束之后,需要能够找到它、查看它、编辑它。
在Scene里查看约束
编辑窗口会显示作用于当前选择的View的约束。通过线的形状
、颜色
和类型
说明当前约束的当前状态。
-
I-bars(两端是T型的线):I-bars显示间距的大小。可能是两个控件之间的大小,可能是一个控件的高度或者宽度。
-
Plain Line(一条普通的直线):Plain Line显示控件边缘的对齐方式。例如,两个或两个以后的控件是左对齐的,那么,这条线会连接着这些控件,并且与它们的Leading之间的间距为0。
-
Solid Line:实线表示这个约束是Required,即priority == 1000。
-
Dash Line:虚线表示这个约束是Optional,即priority < 1000。
-
Red Line:红线表示被约束的影响的控件的约束设置有错误。具体的原因可以点击大纲中每个Scene的右边的箭头查看。这时,箭头是红色的。
-
Orange Line:橘黄色的线表明,Auto Layout根据已有约束计算出来的frame和当前Scene中设置的frame不同。这时,大纲中的右边的箭头是黄色的。可以用
Resolve Auot Layout Issues
->Update frames
来进行修正位置。 -
Blue Line:蓝色的线表示当前的约束设置是正确的,并且控件的位置和Auto Layout计算出来的位置是一样的。
-
Equal Badges:相等标记表明两个控件的宽度或者高度是相同的。并且标记中包含
=
符号。 -
Greater-than-or-equal and less-than-or-equal badges:和Equal Badges类似,它们是标记约束的关系是大于等于或者小于等于的,同时也会显示对应的符号。
快速找到并编辑添加的约束
所有添加的约束都陈列在大纲里。这些约束以伪代码的形式呈现。当选择一个约束时,会在Scene中高亮显示,可以帮助我们快速找到它。而且,可以在右侧的Show the Size inspector
下对约束的Constant
、Priority
、Multiplier
、Relation
、Identifier
、Placeholder
属性进行编辑。
一旦UI变得复杂之后,我们用这种方式找约束就会显得很吃力。Show the Size inspector
工具可以显示出在当前选中的控件上添加的约束。约束的一部分属性也可以在这里进行修改。
注意:这里虽然都是在Show the Size Inspector
下进行修改,但是前者是选择一个约束,后者是选择一个控件。
Auto Layout在iOS中的特性
iOS在与Auto Layout方面有一些独有的特性,包括top and bottom layout guides
、Layout Margins
。
Top and Bottom Layout Guides
top and bottom layout guides表示当前的ViewController从最上面到最下面的可见范围。如果不希望显示的内容在UIKit bars
(例如status bar,navigation bar,tab bar)下面,那么就可以和上下的layout guide来设置约束。
layout guides是遵守UILayoutSupport
协议的。这个协议有一个length
属性,来表示guide和root view(root view就是ViewController默认添加的view)边缘的距离:
-
对于top layout guide,length指明ViewController的root view的上边缘和覆盖在root view上的bar(例如status bar和navigation bar)的底部的距离。
-
对于bottom layout guide,length指明ViewController的root view的下边缘和覆盖在root view上的bar(例如tab bar)的顶部的距离。
在iOS 9中,guide也可以像控件一样,支持用top
、bottom
、height
设置约束。比如,用top layout guide的bottom
属性和bottom layout guide的top
属性与view设置约束。UILayoutSupport
还提供了topAnchor
、bottomAnchor
、heightAnchor
属性,可以让我们用代码的形式来设置约束。
如果layout guides
是view最近的"控件",那么系统会自动将layout guides
作为设置约束的对象。当使用Pin Tool时,可以在layout guides
和root view的上下边缘
进行选择。
Layout margins
Auto Layout为每一个view都定义了margin。margin指的是控件显示内容部分的边缘和控件边缘的距离。就像“回”这个汉字一样,外面的“口”就是控件的外边缘
,里面的“口”是控件显示内容的部分的边缘,我暂且称它为内边缘
,这两个边缘之间的距离就是margin
。
可以用layoutMargins
或者layoutMarginsGuide
属性获得view的margin。layoutMargins
允许获取或者设置UIEdgeInsets
结构的margin,layoutMarginsGuide
则只会获取到只读
的UILayoutGuide对象。
每一个view的默认的margin是8。可以根据APP的需要进行修改。__系统给ViewController的root View设置的margin则不能修改__。root View的上下的margin为0,左右的margin为20。
当使用Control-Dragging
方式,给一个View和它们父视图设置约束时,默认使用内边缘,而不是外边缘。当使用Pin Tool
时,如果Constraint to margins
被勾选,则会使用父视图的内边缘
作为设置约束的参照,如果没有被勾选,则使用外边缘
作为设置约束的参照。所见到的效果就是两种参照下的约束的Constant
的值相差一个margin
。
当在Interface Builder中编辑约束时,First Item
和Second Item
的弹出菜单中可以选择Relative to margin
。如果勾选,会在top
、leading
等属性后面加上Margin
,变成topMargin
、leadingMargin
等,意味着约束的设置参照View的内边缘而不是外边缘。
设置约束时的一些建议
官方文档为我们提供了一些设置约束时的建议
-
__在最相近的两个控件之间创建约束。__假如有3个Button,分别是first,second,third。可以约束first的右边缘和second的左边缘的距离,second的右边缘和third的左边缘的距离。而不要约束first的右边缘和third的左边缘距离,中间隔了一个second。
-
__避免设置固定的高度和宽度。__Auto Layout是动态响应布局的变化。一个控件如果设置固定尺寸,那么就会失去自动调整的能力。
-
在
update frames
时注意,如果这个控件没有设置足够多的约束来确定它的位置和尺寸,那么可能会导致一些意想不到的后果。比如会跑到屏幕外,或者因为宽度、高度为0而消失等等。 -
给所有的控件取一个有意义的名称。在使用工具时,可以方便地辨别出View。
-
使用
leading
和trailing
代替left
和right
。 -
当使用代码创建约束时,确保将它们的
translatesAutoresizingMaskIntoConstraints
属性为NO
。默认情况下,系统会基于控件的frame自动创建一系列约束。当你添加自己的约束时,不可避免地会和自动生成的约束产生冲突。
参考内容:
苹果官方文档:Auto Layout Guide
本文中的图片均取自苹果官方文档