原文:Beginning Auto Layout Tutorial in Swift: Part 1/2,译者:@TurtleFromMars
开始用自动布局约束的方式思考吧!
更新记录:该教程由Brad Johnson更新Swift和iOS 8内容,原文第一版作者为教程编纂组的Matthijs Hollemans。
你可曾为了让App在横竖屏模式下都能展现整洁的界面而感到苦恼?你可曾为了让布局同时支持iPhone和iPad而感到心烦?别灰心,好消息来啦!
为某种确切尺寸的屏幕设计用户界面并不麻烦,但如果屏幕画面的框架不固定,为适应新环境,App中各个UI元素的位置和大小都需要相应调整。
Auto Layout(自动布局)的发展过程可圈可点。Xcode 4首次引入自动布局,如果当年浅尝辄止,现在你真的应该再给Xcode 6一次机会。Xcode 5和iOS 7曾大幅优化自动布局功能,而现在随着两款不同尺寸的大屏新iPhone发布,Xcode 6和iOS 8继续做出改进。对每个iOS开发者来说,自动布局已经成为一个必不可少的工具。
自动布局不仅简化了适配不同屏幕尺寸的工作,而且省去了不少国际化(internationalization)的麻烦,再也不用为你希望支持的各种语言创建新的nib或Storyboard,其中包括希伯来文和阿拉伯文这类从右向左行文的语言。
喝杯咖啡提提神,来包辣条压压惊,准备开始自动布局大师的修行之旅吧!
“弹簧支撑杆”的问题
打开Xcode 6,新建一个基于Single View Application(单视图应用)模板的iPhone Swift项目,命名为"StrutsProblem":
你可能很熟悉Autosizing Mask(自动缩放掩码),也就是所谓的"弹簧支撑杆(springs and struts)"模型。当一个视图的父视图尺寸发生变化时,自动缩放掩码会决定它如何做出调整。其中,"支撑杆"即外边距(margin)是固定还是可变,而"弹簧"即它的宽度和高度如何调整。
例如,如果采用弹性宽度,当父视图变宽时,子视图也会按比例拉宽;如果采用固定右边距,子视图的右边缘就会与父视图的右边缘始终保持相对固定。
一般来讲,自动缩放机制完全可以满足简单需求,但对于复杂的布局问题就没辙了。我们再来举个例子,在这种情况下"弹簧支撑杆"模型无能为力:
点击Main.storyboard在Interface Builder中打开,在继续进行之前,请先在这个Storyboard中禁用Auto Layout(自动布局)和Size Classes(尺寸归类),这两个选项在文件检查器(File inspector)中,六个标签的第一个。
取消选择Use Auto Layout的同时会告知尺寸归类也无法使用,没关系,确认后Storyboard会改用以前的弹簧支撑杆模型。
注:使用Xcode 4.5或更新版本新建的任何nib或Storyboard文件都默认激活自动布局。由于自动布局是自iOS 6引入的特性,如果想用最新的Xcode构建兼容iOS 5的App的话,你需要在所有新加的nib和Storyboard中取消选择"Use Auto Layout"以禁用自动布局。而尺寸归类兼容iOS 7及更新的系统。
在主视图上拖入三个视图,然后如图排列:
为方便辨认,给每个视图设定不同的颜色。
每个视图都内嵌(inset)于窗口(window),距边缘20点。视图之间的间距也是20点。最下面的视图宽度为280点,上面的两个视图宽度均为130点。所有视图高度均为254点。
如果不想拖来拖去,你也可以打开尺寸检查器(size inspector)用键盘输入设置以下属性值:
-
Top left: Origin 20,20, Size 130 x 254
-
Top right: Origin 170,20, Size 130 x 254
-
Bottom: Origin 20,294, Size 280 x 254
借助新功能Preview Assistant(预览助理),不必运行App就可以看到布局的实际效果。打开辅助编辑器(Assistant editor),然后在辅助编辑器顶部的选栏(一般会显示"Automatic")中选择Preview/Main Storyboard:
在预览助理中,你可以随意添加多个模拟设备,是不是比在不同的设备模拟器上分别构建运行要方便多了?点击左下角的+按钮添加设备,配置两个4英寸iPhone屏幕,并用设备名旁边的按钮将其中一个旋转为横屏模式。
App在横屏模式下看起来不大对劲:
实际上你希望的横屏画面是这样:
显然,三个视图的自动缩放掩码都没有完全满足需求。如图修改左上视图的自动缩放设置:
这会让该视图贴靠上边缘和左边缘(不贴靠下边缘和右边缘),并在父视图改变尺寸时横纵缩放。
与此类似,修改右上视图的自动缩放设置:
还有下面的视图:
你会看到预览助理中的布局发生了变化,现在大概是这样:
好不少了,但还差一点。视图间距不正确,或者说视图的位置大致正确但大小有问题。问题在于,当父视图尺寸改变时,自动缩放掩码只是通知子视图去调整大小,但无法告知子视图应该调整多少。
你可以继续试验自动缩放掩码,例如,修改弹性宽高设置("弹簧"),但都无法达到我们预期的结果(每个视图之间相隔20点)。
很不幸,在"弹簧支撑杆"机制下要解决这类布局问题只能写代码。
在旋转用户界面之前、期间、之后,UIKit都会向你的视图控制器发送若干消息,你可以利用这些消息完成用户界面的布局调整。通常的做法是重写viewWillLayoutSubviews,修改需要重整的视图框架。
在重写方法之前,你还必须创建指向需要重整的视图的outlet属性。
点击Xcode工具栏上编辑器工具组中间的按钮切换到辅助编辑器模式,然后按住control将对应的三个视图都拖到ViewController.swift中:
把相应视图分别连接到这三个属性:
1
2
3
|
@IBOutlet weak
var
topLeftView: UIView!
@IBOutlet weak
var
topRightView: UIView!
@IBOutlet weak
var
bottomView: UIView!
|
在ViewController.swift中添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
override func viewWillLayoutSubviews() {
if
UIInterfaceOrientationIsLandscape(self.interfaceOrientation) {
var
rect = self.topLeftView.frame
rect.size.width = 254
rect.size.height = 130
self.topLeftView.frame = rect
rect = self.topRightView.frame
rect.origin.x = 294
rect.size.width = 254
rect.size.height = 130
self.topRightView.frame = rect
rect = self.bottomView.frame
rect.origin.y = 170
rect.size.width = 528
rect.size.height = 130
self.bottomView.frame = rect
}
else
{
var
rect = self.topLeftView.frame
rect.size.width = 130
rect.size.height = 254
self.topLeftView.frame = rect
rect = self.topRightView.frame
rect.origin.x = 170
rect.size.width = 130
rect.size.height = 254
self.topRightView.frame = rect
rect = self.bottomView.frame
rect.origin.y = 295
rect.size.width = 280
rect.size.height = 254
self.bottomView.frame = rect
}
}
|
该回调方法会在视图控制器旋转到新的方向模式时触发,检查视图控制器旋转到的方向并适当调整视图尺寸,本例中采用对应已知iPhone屏幕规格的固定参数。该回调方法在一个动画block中进行,所以尺寸改变过程有动画效果。
现在先别运行,你还需要如图重设三个视图的自动缩放掩码设置,否则自动缩放机制会与viewWillLayoutSubviews方法中设置的位置和大小产生冲突:
现在应该可以了。在iPhone 5模拟器上运行App(代码在预览助理中不会生效!),切换到横屏模式。现在视图布局规整,切回竖屏模式,检查显示是否正常。
确实实现了,但如此简单的布局问题就需要写数十行代码,对于复杂的布局问题,特别是每个单独的视图都会改变大小,或者子视图的数量不定的动态情况,代码何等繁乱,可想而知。
这回在3.5英寸模拟器上运行试试。糟,视图的位置和大小都有问题,因为之前在viewWillLayoutSubviews中编码的坐标数据基于4英寸iPhone屏幕规格(320×568而不是320×480,单位为点)。你可以加一条if语句判断屏幕规格,然后再写一组不同的坐标数据,但你明白,这种方法越来越不靠谱了。
另一种解决方法是为竖屏和横屏分别创建不同的nib。当设备旋转时,从另一个nib载入并替换掉当前的视图。不过这一方案的工作量也很大,而且引入两个nib不利于后期维护,如果使用的是Storyboard而不是nib的话,这个方案就更不现实了。
自动布局来救场!
你马上就会看到如何利用自动布局实现同样效果。首先从ViewController.swift删除viewWillLayoutSubviews,接下来的工作一行代码也不用写!
选择Main.storyboard,然后在文件检查器面板中选中Use Auto Layout和Use Size Classes选项,为当前Storyboard开启自动布局和尺寸归类:
什么是尺寸归类
(译注:本译文中,在谈论功能特性时,Size Classes翻译为"尺寸归类";而谈论实际应用情境下的某种具体Size Class时,翻译为"尺寸类"。)
尺寸归类(Size Classes)是iOS 8和Xcode 6的一项超赞的全新特性。有了直观的尺寸归类,只要一个Storyboard就可以通吃一个通用App(Universal App)。几乎屏幕上可见的任何东西都可以有尺寸类,包括屏幕(UIScreen),视图,还有视图控制器。尺寸类有两大类型:垂直和水平。每个垂直的或水平的尺寸类的值可以为 常规(Regular)、紧缩(Compact)、任意(Any) 三种之一。
尺寸类的组合对应App运行的设备及其方向环境。例如,竖屏iPhone对应常规高度、紧缩宽度。"任意"即泛用尺寸类,你可以把它当成其他所有布局的父类,如果当前设备和方向对应的尺寸类中没有定义,Storyboard会按照"任意"中的配置进行布局。
在Xcode 6中查看和切换尺寸类配置非常容易。在Storyboard面板底部中间有一个显示"w_Any_h_Any_"的文本标签,点击就可以看到尺寸类配置网格:
你可以在网格中其他的方块之间移动鼠标指针,观察它们分别对应什么尺寸类。默认状态为任意宽度、任意高度,即泛用尺寸类配置。Apple建议在该尺寸类中为通用App完成所有的初始界面布局,因为所有的尺寸类都建立在泛用尺寸类基础之上。请确保当前Storyboard中选定的尺寸类为w_Any_h_Any。
你会注意到Storyboard中的场景变成了正方形,反映我们的泛用尺寸类配置。
本教程仅包括自动布局的基础内容,要想了解关于尺寸归类的更多细节,请参阅我们的自适应布局入门教程。
你的第一个自动布局约束
在预览助手中观察横屏布局,目前大概是这样:
接下来我们要将自动布局付诸实践。按住command键分别点击上面的两个视图(绿色的和黄色的),同时选中,然后在Xcode的Editor(编辑器)目录下选择Pin/Widths Equally(固定/相同宽度):
再次选中这两个视图,选择Editor/Pin/Horizontal Spacing(编辑器/固定/水平间隔)。(虽然在执行第一个固定操作后,两个视图看起来依然是选中状态,但此时处于一种特殊的布局关系显示模式,所以有必要重新选定相应视图。)
现在的Storyboard形如下图:
那个橙色的“工字梁”代表视图之间的约束(constraint)。至今在两个视图上添加了两个约束,一个是两者的等宽约束(Equal Widths,由等号图标表示),还有一个是两者之间的水平间隔约束(Horizontal Space)。约束可以表达视图之间的空间关系,是使用自动布局构建布局的主要工具。五花八门的约束乍一看可能有点吓人,但只要了解含义,约束其实非常浅显易懂。
按以下步骤继续构建布局,每一步都会新增橙色的约束,请务必在添加每个约束后重新选中相应视图。
对于左上的绿色视图,从Editor/Pin目录中选择:
-
Top Space to Superview(与父视图的顶端间隔)
-
Leading Space to Superview(与父视图的首部间隔)
对于右边的黄色视图,选择:
-
Top Space to Superview(与父视图的顶端间隔)
-
Trailing Space to Superview(与父视图的尾部间隔)
还有下面的蓝色视图:
-
Leading Space to Superview(与父视图的首部间隔)
-
Trailing Space to Superview(与父视图的尾部间隔)
-
Bottom Space to Superview(与父视图的底端间隔)
(译注:Leading(首部)/Trailing(尾部)与Left(左)/Right(右)的主要区别在于,Left/Right指绝对的左右,而为了方便界面国际化,Leading/Trailing概念的实际方向与语言的行文方向相关,例如,一般来说,适配阿拉伯文的部分UI元素在水平方向上也是要从右向左排列的,所以在我们看来,某些阿拉伯语界面就好像左右反转一样。)
现在应该有以下约束了:
注意部分约束依然为橙色,意思是布局设置不彻底,自动布局为计算视图位置和大小还需要更多约束。解决办法就是继续合理添加约束,直到所有约束呈蓝色。
按住command键,把三个视图都选中,在Editor菜单中选择Pin/Heights Equally(固定/相同高度)。
现在与之前类似,按住command选中左上角和下面的视图,选择Editor/Pin/Vertical Spacing(编辑器/固定/垂直间隔)。
Interface Builder显示如图:
约束都变蓝了,大成功!现在提供的信息已经足够让自动布局计算出一个有效布局方案,但还有点问题,在切换到泛用尺寸类时页面右侧有大片空白。现在选择下面的视图,然后选择尾部间隔约束:
打开尺寸检查器,将Constant(常量)值修改为20:
为右上的视图做同样设置。
观察预览布局,Look!横屏竖屏都很棒,而且通吃各种尺寸,3.5英寸、4英寸、4.7英寸、5.5英寸的屏幕都没问题。在预览助理中加入几个设备看看吧。你现在可能注意到iPad也是可选的,把它也加进去,你会发现每种设备都可以靠一个布局搞定!
爽!但刚才所做的究竟是什么呢?自动布局会让你描述布局中的视图与其他视图或父视图之间构成怎样的空间关系,而不是亲自用代码写定视图的大小和位置坐标。
你已经在布局中添加了如下的关系,即约束:
-
左上的视图与右上的视图宽度保持一致。(即开头的固定相同宽度命令。)
-
左上的视图和右上的视图之间有20点水平间距。(即固定水平间隔。)
-
所有视图高度保持一致。(即固定相同高度。)
-
上面的两个视图和下面的视图之间有20点的垂直间距。(即固定垂直间隔。)
-
视图和屏幕边缘之间有20点间距。(即与父视图的顶端、底端、首部、尾部间隔。)
这些约束足以向自动布局表达视图所在的位置特征,还有当屏幕尺寸改变时的行为。
你可以在左侧的文档大纲(Document Outline)中查看所有约束,为Storyboard启用自动布局时Xcode会在其中加入Constraints(约束)部分。如果未显示大纲面板,请点击Interface Builder窗口下面的箭头按钮。
在文档大纲中点击一个约束,Interface Builder会用白边和阴影突出显示约束在视图中的位置:
约束是实际存在的`NSLayoutConstraint`对象,也包含相关属性。例如,选择在上面两个视图之间创建间隔的那个约束(名为"Horizontal Space (20)",请确保正确选择),然后可以在属性检查器中编辑Constant字段值来修改间距。
设为100,在预览助理中观察变化,现在间距明显增大了:
在App中描述视图的空间特征时,自动布局的表达能力远强于弹簧支撑杆模型。本教程接下来会带你了解各种约束,还有如何应用约束构建不同的布局。
自动布局的工作原理
如上所见,自动布局的基本工具是约束。一个约束可以描述两个视图之间的几何关系。例如,有如下约束:
“Label A的右边缘与Button B的左边缘之间保持20点的距离。”
自动布局将所有约束纳入考虑范围,并用数学方法计算出所有视图各自的理想位置和尺寸。你再也不用自己设置视图的边框了,自动布局会完全凭借你在视图上设定的约束来为您代劳。
从前,开发者只能写定视图的框架,要么在Interface Builder里设置具体坐标,要么把一个矩形传给`init(frame:)`方法,要么直接设置视图的frame,bounds或center属性。
比如,在刚刚构建的App中,你需要如下指定视图框架:
还要设置各个视图的自动缩放掩码:
现在我们再也不用这么考虑了。借助自动布局,你只需要设定:
如今,视图的具体大小和位置都已不重要。当然,在向面板中拖入一个新控件的时候依然会有确切的大小和位置,但现在的主要作用是辅助设计,用来提示Interface Builder在何处设置约束。
自动布局简化工作的基本思想是:基于开发者设置的一些必要常量(如20点间距,或某图片视图的确切宽度),以相对的方式自动构建其余布局。
意图导向的设计
使用约束的一大好处是,你再也不用为了在适当的位置显示视图而来回折腾坐标值。现在,你可以向自动布局描述视图的几何关系,之后自动布局就能为你搞定一切。这就是“意图导向的设计”。
在进行意图导向设计时,你表达的是你要实现“什么”效果,而不必描述“如何”实现。以前你可能会这样描述:“这个按钮左上角的坐标位于(20, 230)”,而现在你会这样表达:
“这个按钮相对父视图垂直居中,并与父视图的左边缘保持一定固定距离。”
无论父视图大小,如此描述,自动布局都会自动计算好按钮应该在哪里显示。
再举几个意图导向设计的例子,这类指示都可以由自动布局完美解决:
“这两个文本字段的大小应该保持一致。”
“这两个按钮应该同时移动。”
“这四个文本标签应该右对齐。”
这样一来,用户界面设计的描述性大大增强。单纯定义约束,系统就会自动为你计算框架。
如第一小节中所见,之前甚至是合理构建只含几个视图的简单横竖屏布局都需要相当的工作量。自动布局可以有效节省时间和精力。只要适当设置约束,布局方案不用任何修改就可以正常适配横竖屏。
使用自动布局的另一个重要好处是国际化。以德语为例,德文以臭长的复合词著名(如 圆珠笔 Kugelschreiber,隐私设置 Datenschutzeinstellungen,春分 Frühjahrstagundnachtgleiche),要把这一坨塞进一个文本标签是何等蛋疼,而现在一身轻松,自动布局会在符合约束的前提下根据需要显示的内容自动调整大小。
现在添加德语、法语等其他语言支持的主要工作也就是设置约束、翻译文本,就这些!
自动布局不仅对设备旋转有效,还很容易收放界面,适应不同屏幕尺寸。这项技术在配备更长屏幕的iPhone 5发布时加入iOS并非巧合,现在我们还有更大的iPhone 6和6 Plus!
有了自动布局,在大屏手机上拓展用户界面容易了不少,结合iOS 7引入的动态字体(Dynamic Type,支持该特性的App可以响应用户的系统全局字体大小设置并做出调整),自动布局愈发重要。配合自动布局,字体调整也易于适配。
学习自动布局有一诀窍:熟能生巧,接下来本教程会带你玩转自动布局。
你好,约束
关闭当前项目,试用Single View Application模板新建一个iPhone项目,命名为“Constraints”。
Xcode 6创建的任何新项目都默认使用Auto Layout,不必手动开启。为了简化这部分内容,请在项目中打开Main.storyboard并禁用Size Classes。
首先,拖入一个新的按钮(Button),注意在面板上拖动时会显示蓝色虚线,这些线叫做辅助线(guides):
在屏幕页面边缘附近和中心都有辅助线:
如果你用过Interface Builder,那你一定见过这些辅助线,在排列和对齐视图时很有帮助。
注意,向视图添加新对象的时候是不存在任何约束的!但这怎么成?我们刚刚了解到自动布局为确定所有视图的位置和大小需要足够的约束,但现在什么约束都没有。也就是说现在的布局不完整咯?
其实,如果不采用任何约束,Xcode会自动分配一组默认约束,即自动约束(automatic constraints),但不会在设计时生效,而是在App编译构建时进行。自Xcode 5以来,自动布局致力于让开发者专心设计界面,而不在设计过程中干扰开发者,这一点很讨人喜欢。
自动约束会为视图赋予固定的位置和大小,也就是说,和你在Storyboard中看到的坐标保持一致,这一点其实很便利,因为这样你可以在很大程度上忽略自动布局。你完全可以只为有特殊需要的部分视图创建约束,其余部分只要能行就可以保持默认,原封不动。
好,我们来玩玩看约束可以做什么。刚才拖上去的那个按钮在左上角,无约束。请确保按钮与两条辅助线对齐。
使用Editor/Pin菜单在按钮上添加两个新约束,结果像这样:
猜中了吗?两个约束分别是与父视图的首部间隔(Leading Space to Superview)和与父视图的顶端间隔(Top Space to Superview)。
所有约束都在Interface Builder窗口左手边的文档大纲面板中列出:
现在存在两个约束:一个是在按钮和主视图左边缘之间的水平间隔,还有一个是在按钮和主视图顶边缘之间的垂直间隔。这两条约束表达的关系是:
“该按钮在父视图中始终距离左上角20点。”
注:实际上在刚才的情景下这两个约束用途不大,因为显示结果和自动约束一样。上述情况下,如果你只是想让按钮始终相对父视图左上角隔一段距离,你也完全可以不添加任何约束,让Xcode使用默认的自动约束。
现在选中按钮,把它拖到场景右上角,同样吸附蓝色的辅助线:
卧槽,那橙色的东西是什么鬼?这个表示Interface Builder中的按钮位置大小与自动布局根据约束期望的位置大小不再保持一致,叫做视图错位(misplaced view)。
运行App,按钮依然显示在屏幕左上角:
对自动布局来说,橙色代表错误。Interface Builder中会显示两个橙色方框:一个虚线一个实线。虚线框表示依照自动布局的视图框架,实线框表示依照场景中布置位置的视图框架。两种应该达成一致,但这里产生了分歧。
如何修复这个问题取决于你的实际意图:
-
想把按钮固定在距左边254点的位置?该情况下需要将现有的水平间隔常量增大234点,那个写着“+234”的橙色标章表达的就是这个意思。
-
想把按钮改为固定在右边?该情况下需要删除现有约束并新建约束。
删除水平间隔约束。首先在面板或文档大纲中选中,然后按下键盘的delete键。
注意按钮周围的橙色框,还有红色点线框。橙色框告诉你布局有误,红色点线框告诉你自动布局认为在运行时该按钮会在何处显示。出现这种情况是因为计算按钮位置所需的约束不完整,你还需要为水平位置添加约束。
你也许想知道这里Xcode为何不在水平位置添加自动约束吧,原因是,仅在没有任何人为约束时,Xcode才会为其创建自动约束。也就是说,一旦你在某个视图上手动添加了一个约束,Xcode就会把该视图的布局约束设置全权交给你。此时Xcode不会为其创建任何自动约束,而是让你为这个视图添加布局所需的其他约束。
选中这个按钮,然后选择Editor/Pin/Trailing Space to Superview。这会在按钮的右边缘和屏幕右边缘之间新建一个约束,目前所有约束表达的关系如下:
“该按钮在其父视图中始终距离右上角20点。”
运行App,切换到横屏模式,注意按钮会与屏幕右边缘保持同样的距离:
贴靠辅助线放置一个按钮(或其他视图)并添加约束时,你会得到一个预设的间隔约束,预设标准间距的定义出自Apple官方文档《iOS人机交互指南》(iOS Human Interface Guidelines,简称HIG),对边缘附近而言,间距的标准大小是20点。
现在把按钮向左拖移一段距离:
橙色虚线框又出现了,视图错位嘛。假定我们想把按钮安排在这个新位置,毕竟设好约束后再让视图微调几个像素点也不是什么稀罕事,有一种解决办法是把现有约束删掉然后新建约束,但还有更快捷的办法。
Editor菜单中有一个Resolve Auto Layout Issues(化解自动布局问题)子菜单,在其中选择Update Constraints(更新约束)。在我这里呢,该命令会告知Interface Builder将相应约束增大64点,如下:
好,约束再次变蓝,布局有效。你可以在文档大纲中看到,水平间隔约束已经不是预设的标准间距了。
至今我们把水平间隔约束和垂直间隔约束摆弄了一番,接下来还有居中约束。向面板底部中心附近拖入一个新的Button对象,使其吸附辅助线:
想让按钮始终在水平方向上相对父视图居中,你需要添加的约束是Center X Alignment(X轴中心对齐)。从Editor菜单选择Align/Horizontal Center(对齐/水平中心),面板上会出现一条橙色长线。
长线显示橙色是因为你只设定了按钮的X坐标相关约束,Y坐标尚未指定。在Editor/Pin菜单中添加约束,让按钮与底边隔开一段距离,像这样:
选对了吗,刚刚添加的是Bottom Space to Superview约束。这条垂直间隔约束可以让按钮和父视图底边保持一定距离(默认为标准间距)。
运行App,切到横屏模式。现在在横屏模式下按钮也会位于屏幕底部的中心了:
之前所做就是表达“该按钮始终位于底部中心”这个意图。注意你完全不需要把按钮的具体坐标告诉Interface Builder,只需要描述你想让它定在视图中的什么地方。
自从用了自动布局,你就再也不该为安排视图的确切坐标和大小操心了,与此相对,自动布局会根据你设定的约束自动推算出合理的坐标和大小。
下图分别是自动布局启用后和之前在尺寸检查器中的不同反映,这是思维方式的转变:
如果不使用自动布局,在相应字段中输入X坐标,Y坐标,宽度,高度值会改变所选视图的位置和尺寸。如果使用自动布局,你依然可以在其中输入具体数值,但在视图已经拥有约束的情况下这样做会造成视图错位,需要更新约束,匹配新值。
例如,将该按钮宽度值设为100,面板会如下显示:
Xcode清楚表明,“想把宽度设成100点吗?没问题,但你要知道这不符合约束哦。”
这里我们假定,确实想把按钮宽度设为100点。对此有一种特殊类型的约束:Fixed Width(固定宽度)约束。首先执行撤销操作,让按钮恢复居中,所有约束呈蓝色。选中按钮,选择Editor/Pin/Width会在按钮下方新增一条“横梁”:
选择这条“横梁”,然后在属性检查器中设Constant为100,这会强制将该按钮宽度设定为100点,无论其中的文字大小。为方便辨认,可以为按钮设定背景色:
你也可以在左侧的文档大纲中看到这条宽度约束:
与其他表示该按钮与父视图空间关系的约束不同,宽度约束仅对该按钮自身有效,或许你可以视其为这个按钮和…它自己之间的约束(笑)。
你也许会问,为什么之前这个按钮没有宽度约束呢?在没有宽度约束的情况下,自动布局如何确定按钮的宽度?
真相只有一个:按钮知道自己有多宽。按钮文字附加预留空间,以此为基础计算宽度。如果你在按钮上设置了背景图片,图片也会纳入考虑。
这就是固有内容尺寸(intrinsic content size)。并不是所有控件都有这个概念,但也不少(比如UILabel也有)。如果一个视图能够自行计算尺寸,你就不必为其指定宽度约束或高度约束。后面我们还会提到。
要让按钮回到最优尺寸,请先删除宽度约束,然后选中按钮,选择Editor菜单里的Size to Fit Content(适应内容设置尺寸),恢复按钮的固有内容尺寸。
一个巴掌拍不响
辅助线不仅存在于父子视图之间,还存在于同一视图层级的不同视图之间。我们来演示一下,向面板中添加一个新按钮,然后拖动这个按钮,你会注意到,靠近其它按钮时会出现辅助线。
把新按钮拖到已有按钮附近,使其吸附辅助线:
这里有好几条辅助线,Interface Builder会识别两个按钮不同的对齐方式:顶端对齐、中心对齐或是底线对齐。
Xcode 4可能会把其中的辅助线吸附状态转换成一个新约束,但从Xcode 5开始,如果想在两个按钮之间设置约束,你需要手动创建,就像前面一样在Editor/Pin菜单中选择命令,其实还有一种更快捷的办法。
选中新按钮,按住control拖到另一个按钮上:
松开鼠标按键,会弹出一个选单,点击第一个选项,Horizontal Spacing(水平间隔)。
该操作会新建一个约束:
出现橙色方框,按钮还需要另外的约束。按钮的大小可以通过固有内容尺寸自动推断,刚刚添加的是X轴位置约束,那剩下的就是Y轴位置约束了。
在这里我们很容易断定缺少的约束是什么,但在更复杂的设计中可能并不明显。然而Xcode早已看穿一切,会明确指出缺少的约束,真是帮大忙了。
文档大纲里在View Controller Scene旁边有个小红箭头,点击箭头会列出所有的自动布局问题。
不错!缺少Y轴位置约束,我们就来补上。按住control从新按钮向下拖到容器视图上:
这次弹出的选单有点不一样。选单中的选项由环境决定,也就是依赖于相关视图和鼠标移动方向。选择Bottom Space to Bottom Layout Guide(与底端布局指示线的底端间隔)。(译注:Top/Bottom Layout Guide即把固有界面要素(如分页栏、工具栏、导航栏等)考虑在内的上下布局边缘,例如,当该页面下方存在分页栏时,底端布局指示线与分页栏的上边缘重合,而当页面不存在任何栏时,底端布局指示线与屏幕下边缘重合。)
现在,新按钮距屏幕底端有一定垂直间隔,与另一个按钮之间有一定水平间隔。由于空间相对较小(仅8点),“工字梁”可能有些看不清,但确实存在。
在文档大纲中点选Horizontal Space (8)约束:
这条约束存在于两个按钮之间,表达的含义是:
“无论第一个按钮在何处、有多大,第二个按钮始终显示在第一个按钮的左侧。”
选择右边的按钮,加长其中的文字,比如“A longer label”。完成后,按钮会为文本腾出空间,自动调整大小,而另一个按钮会被它挤开。毕竟设置了与第一个按钮的左边相对固定的约束。
你可以继续尝试,感受相关约束的工作方式。首先给较长按钮的背景设为黄色,然后再拖入一个按钮,放在黄按钮上方,使其在垂直方向上吸附辅助线(这里不要让两个按钮的左边缘对齐)。
为了清楚辨认新按钮的实际范围,将其背景设为绿色。
两个按钮(借助辅助线)吸附在一起,因此两者之间存在HIG建议的8点的标准间隔。按住control在按钮之间拖拽,然后在弹出的选单中选择Vertical Spacing,将标准间隔转换成约束。
控件之间并非仅限于标准间隔,约束和视图一样是货真价实的对象,也就是说约束的属性可以修改。
你可以在面板中点击两个按钮之间的“工字梁”,选中垂直间隔约束。当然这么选多少有点麻烦,最简单的方法还是在文档大纲中进行选择。选中约束后切到属性检查器:
在Constant字段输入40,修改约束大小。
运行App,切到横屏模式,观察布局效果,像之前一样添加横屏预览助理也行:
按钮在垂直方向上的排布得以维持,水平方向却没有。理由显而易见,绿色的按钮还没设置X轴位置约束。
对这个问题而言,添加从绿色按钮到屏幕左边缘的水平间隔约束并不妥当,这样的话在横屏模式下按钮也会保持相同的X坐标,看起来会很别扭。所以呢,我们需要表达这样的意图:
“黄色按钮始终水平居中,绿色按钮与黄色按钮左边缘对齐。”
第一个条件的约束已经设好了,现在我们来搞定第二个。Interface Builder会显示对齐辅助线,拖动上面的按钮,使其左边缘吸附黄按钮的对齐辅助线。
最后,按住control在两个按钮间拖拽,然后在选单中点击Left。这样创建的对齐约束表示“两个视图的左边缘始终对齐”,换句话说,两个按钮的X坐标始终保持一致。约束变蓝,布局问题解决。
运行App,切到横屏模式或者在预览助理中验证布局是否有效:
何去何从?
初试牛刀,感觉如何?可能需要一点时间适应,但自动布局确实会为您省去诸多烦恼,让应用更加灵活!
想了解更多吗?想深入理解自动布局的潜力和可能会遇到的问题吗?请听下回分解:Swift语言Auto Layout入门教程:下篇。
还会用自动布局构建一个在某些App中切实存在的实用布局哦!