Swift 语言 Auto Layout 入门教程

发布于 2021-08-17 18:17:08 字数 41560 浏览 1345 评论 0

Course-Icon.png

原文: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":

01.png

你可能很熟悉Autosizing Mask(自动缩放掩码),也就是所谓的"弹簧支撑杆(springs and struts)"模型。当一个视图的父视图尺寸发生变化时,自动缩放掩码会决定它如何做出调整。其中,"支撑杆"即外边距(margin)是固定还是可变,而"弹簧"即它的宽度和高度如何调整。

例如,如果采用弹性宽度,当父视图变宽时,子视图也会按比例拉宽;如果采用固定右边距,子视图的右边缘就会与父视图的右边缘始终保持相对固定。

一般来讲,自动缩放机制完全可以满足简单需求,但对于复杂的布局问题就没辙了。我们再来举个例子,在这种情况下"弹簧支撑杆"模型无能为力:

点击Main.storyboard在Interface Builder中打开,在继续进行之前,请先在这个Storyboard中禁用Auto Layout(自动布局)和Size Classes(尺寸归类),这两个选项在文件检查器(File inspector)中,六个标签的第一个。

02.png

取消选择Use Auto Layout的同时会告知尺寸归类也无法使用,没关系,确认后Storyboard会改用以前的弹簧支撑杆模型。

注:使用Xcode 4.5或更新版本新建的任何nib或Storyboard文件都默认激活自动布局。由于自动布局是自iOS 6引入的特性,如果想用最新的Xcode构建兼容iOS 5的App的话,你需要在所有新加的nib和Storyboard中取消选择"Use Auto Layout"以禁用自动布局。而尺寸归类兼容iOS 7及更新的系统。

在主视图上拖入三个视图,然后如图排列:

022.jpg

为方便辨认,给每个视图设定不同的颜色。

每个视图都内嵌(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:

04.png

在预览助理中,你可以随意添加多个模拟设备,是不是比在不同的设备模拟器上分别构建运行要方便多了?点击左下角的+按钮添加设备,配置两个4英寸iPhone屏幕,并用设备名旁边的按钮将其中一个旋转为横屏模式。

App在横屏模式下看起来不大对劲:

05.jpg

实际上你希望的横屏画面是这样:

06.jpg

显然,三个视图的自动缩放掩码都没有完全满足需求。如图修改左上视图的自动缩放设置:

06.jpg

这会让该视图贴靠上边缘和左边缘(不贴靠下边缘和右边缘),并在父视图改变尺寸时横纵缩放。

与此类似,修改右上视图的自动缩放设置:

07.png

还有下面的视图:

08.png

你会看到预览助理中的布局发生了变化,现在大概是这样:

09.jpg

好不少了,但还差一点。视图间距不正确,或者说视图的位置大致正确但大小有问题。问题在于,当父视图尺寸改变时,自动缩放掩码只是通知子视图去调整大小,但无法告知子视图应该调整多少。

你可以继续试验自动缩放掩码,例如,修改弹性宽高设置("弹簧"),但都无法达到我们预期的结果(每个视图之间相隔20点)。

10.jpg

很不幸,在"弹簧支撑杆"机制下要解决这类布局问题只能写代码。

在旋转用户界面之前、期间、之后,UIKit都会向你的视图控制器发送若干消息,你可以利用这些消息完成用户界面的布局调整。通常的做法是重写viewWillLayoutSubviews,修改需要重整的视图框架。

在重写方法之前,你还必须创建指向需要重整的视图的outlet属性。

点击Xcode工具栏上编辑器工具组中间的按钮切换到辅助编辑器模式,然后按住control将对应的三个视图都拖到ViewController.swift中:

11.jpg

把相应视图分别连接到这三个属性:

@IBOutlet weak var topLeftView: UIView!
@IBOutlet weak var topRightView: UIView!
@IBOutlet weak var bottomView: UIView!

在ViewController.swift中添加如下代码:

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方法中设置的位置和大小产生冲突:

12.jpg

现在应该可以了。在iPhone 5模拟器上运行App(代码在预览助理中不会生效!),切换到横屏模式。现在视图布局规整,切回竖屏模式,检查显示是否正常。

确实实现了,但如此简单的布局问题就需要写数十行代码,对于复杂的布局问题,特别是每个单独的视图都会改变大小,或者子视图的数量不定的动态情况,代码何等繁乱,可想而知。

这回在3.5英寸模拟器上运行试试。糟,视图的位置和大小都有问题,因为之前在viewWillLayoutSubviews中编码的坐标数据基于4英寸iPhone屏幕规格(320×568而不是320×480,单位为点)。你可以加一条if语句判断屏幕规格,然后再写一组不同的坐标数据,但你明白,这种方法越来越不靠谱了。

另一种解决方法是为竖屏和横屏分别创建不同的nib。当设备旋转时,从另一个nib载入并替换掉当前的视图。不过这一方案的工作量也很大,而且引入两个nib不利于后期维护,如果使用的是Storyboard而不是nib的话,这个方案就更不现实了。

13.jpg

自动布局来救场!

你马上就会看到如何利用自动布局实现同样效果。首先从ViewController.swift删除viewWillLayoutSubviews,接下来的工作一行代码也不用写!

选择Main.storyboard,然后在文件检查器面板中选中Use Auto LayoutUse Size Classes选项,为当前Storyboard开启自动布局和尺寸归类:

14.jpg

什么是尺寸归类

(译注:本译文中,在谈论功能特性时,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_"的文本标签,点击就可以看到尺寸类配置网格:

15.jpg

你可以在网格中其他的方块之间移动鼠标指针,观察它们分别对应什么尺寸类。默认状态为任意宽度、任意高度,即泛用尺寸类配置。Apple建议在该尺寸类中为通用App完成所有的初始界面布局,因为所有的尺寸类都建立在泛用尺寸类基础之上。请确保当前Storyboard中选定的尺寸类为w_Any_h_Any。

你会注意到Storyboard中的场景变成了正方形,反映我们的泛用尺寸类配置。

本教程仅包括自动布局的基础内容,要想了解关于尺寸归类的更多细节,请参阅我们的自适应布局入门教程

你的第一个自动布局约束

在预览助手中观察横屏布局,目前大概是这样:

177.jpg

接下来我们要将自动布局付诸实践。按住command键分别点击上面的两个视图(绿色的和黄色的),同时选中,然后在Xcode的Editor(编辑器)目录下选择Pin/Widths Equally(固定/相同宽度):

18.png

再次选中这两个视图,选择Editor/Pin/Horizontal Spacing(编辑器/固定/水平间隔)。(虽然在执行第一个固定操作后,两个视图看起来依然是选中状态,但此时处于一种特殊的布局关系显示模式,所以有必要重新选定相应视图。)

现在的Storyboard形如下图:

19.jpg

那个橙色的“工字梁”代表视图之间的约束(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元素在水平方向上也是要从右向左排列的,所以在我们看来,某些阿拉伯语界面就好像左右反转一样。)

现在应该有以下约束了:

20.jpg

注意部分约束依然为橙色,意思是布局设置不彻底,自动布局为计算视图位置和大小还需要更多约束。解决办法就是继续合理添加约束,直到所有约束呈蓝色。

按住command键,把三个视图都选中,在Editor菜单中选择Pin/Heights Equally(固定/相同高度)。

现在与之前类似,按住command选中左上角和下面的视图,选择Editor/Pin/Vertical Spacing(编辑器/固定/垂直间隔)。

Interface Builder显示如图:

21.jpg

约束都变蓝了,大成功!现在提供的信息已经足够让自动布局计算出一个有效布局方案,但还有点问题,在切换到泛用尺寸类时页面右侧有大片空白。现在选择下面的视图,然后选择尾部间隔约束:

22.jpg

打开尺寸检查器,将Constant(常量)值修改为20:

23.png

为右上的视图做同样设置。

观察预览布局,Look!横屏竖屏都很棒,而且通吃各种尺寸,3.5英寸、4英寸、4.7英寸、5.5英寸的屏幕都没问题。在预览助理中加入几个设备看看吧。你现在可能注意到iPad也是可选的,把它也加进去,你会发现每种设备都可以靠一个布局搞定!

24.jpg

爽!但刚才所做的究竟是什么呢?自动布局会让你描述布局中的视图与其他视图或父视图之间构成怎样的空间关系,而不是亲自用代码写定视图的大小和位置坐标。

你已经在布局中添加了如下的关系,即约束:

  • 左上的视图与右上的视图宽度保持一致。(即开头的固定相同宽度命令。)
  • 左上的视图和右上的视图之间有20点水平间距。(即固定水平间隔。)
  • 所有视图高度保持一致。(即固定相同高度。)
  • 上面的两个视图和下面的视图之间有20点的垂直间距。(即固定垂直间隔。)
  • 视图和屏幕边缘之间有20点间距。(即与父视图的顶端、底端、首部、尾部间隔。)

这些约束足以向自动布局表达视图所在的位置特征,还有当屏幕尺寸改变时的行为。

27.jpg

你可以在左侧的文档大纲(Document Outline)中查看所有约束,为Storyboard启用自动布局时Xcode会在其中加入Constraints(约束)部分。如果未显示大纲面板,请点击Interface Builder窗口下面的箭头按钮。

在文档大纲中点击一个约束,Interface Builder会用白边和阴影突出显示约束在视图中的位置:

28.png

约束是实际存在的`NSLayoutConstraint`对象,也包含相关属性。例如,选择在上面两个视图之间创建间隔的那个约束(名为"Horizontal Space (20)",请确保正确选择),然后可以在属性检查器中编辑Constant字段值来修改间距。

29.png

设为100,在预览助理中观察变化,现在间距明显增大了:

30.png

在App中描述视图的空间特征时,自动布局的表达能力远强于弹簧支撑杆模型。本教程接下来会带你了解各种约束,还有如何应用约束构建不同的布局。

自动布局的工作原理

如上所见,自动布局的基本工具是约束。一个约束可以描述两个视图之间的几何关系。例如,有如下约束:

“Label A的右边缘与Button B的左边缘之间保持20点的距离。”

自动布局将所有约束纳入考虑范围,并用数学方法计算出所有视图各自的理想位置和尺寸。你再也不用自己设置视图的边框了,自动布局会完全凭借你在视图上设定的约束来为您代劳。

从前,开发者只能写定视图的框架,要么在Interface Builder里设置具体坐标,要么把一个矩形传给`init(frame:)`方法,要么直接设置视图的frame,bounds或center属性。

比如,在刚刚构建的App中,你需要如下指定视图框架:

31.png

还要设置各个视图的自动缩放掩码:

32.png

现在我们再也不用这么考虑了。借助自动布局,你只需要设定:

33.png

如今,视图的具体大小和位置都已不重要。当然,在向面板中拖入一个新控件的时候依然会有确切的大小和位置,但现在的主要作用是辅助设计,用来提示Interface Builder在何处设置约束。

自动布局简化工作的基本思想是:基于开发者设置的一些必要常量(如20点间距,或某图片视图的确切宽度),以相对的方式自动构建其余布局。

意图导向的设计

使用约束的一大好处是,你再也不用为了在适当的位置显示视图而来回折腾坐标值。现在,你可以向自动布局描述视图的几何关系,之后自动布局就能为你搞定一切。这就是“意图导向的设计”。

在进行意图导向设计时,你表达的是你要实现“什么”效果,而不必描述“如何”实现。以前你可能会这样描述:“这个按钮左上角的坐标位于(20, 230)”,而现在你会这样表达:

“这个按钮相对父视图垂直居中,并与父视图的左边缘保持一定固定距离。”

无论父视图大小,如此描述,自动布局都会自动计算好按钮应该在哪里显示。

再举几个意图导向设计的例子,这类指示都可以由自动布局完美解决:

“这两个文本字段的大小应该保持一致。”

“这两个按钮应该同时移动。”

“这四个文本标签应该右对齐。”

这样一来,用户界面设计的描述性大大增强。单纯定义约束,系统就会自动为你计算框架。

如第一小节中所见,之前甚至是合理构建只含几个视图的简单横竖屏布局都需要相当的工作量。自动布局可以有效节省时间和精力。只要适当设置约束,布局方案不用任何修改就可以正常适配横竖屏。

使用自动布局的另一个重要好处是国际化。以德语为例,德文以臭长的复合词著名(如 圆珠笔 Kugelschreiber,隐私设置 Datenschutzeinstellungen,春分 Frühjahrstagundnachtgleiche),要把这一坨塞进一个文本标签是何等蛋疼,而现在一身轻松,自动布局会在符合约束的前提下根据需要显示的内容自动调整大小。

现在添加德语、法语等其他语言支持的主要工作也就是设置约束、翻译文本,就这些!

40.jpg

自动布局不仅对设备旋转有效,还很容易收放界面,适应不同屏幕尺寸。这项技术在配备更长屏幕的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):

41.jpg

在屏幕页面边缘附近和中心都有辅助线:

42.jpg

如果你用过Interface Builder,那你一定见过这些辅助线,在排列和对齐视图时很有帮助。

注意,向视图添加新对象的时候是不存在任何约束的!但这怎么成?我们刚刚了解到自动布局为确定所有视图的位置和大小需要足够的约束,但现在什么约束都没有。也就是说现在的布局不完整咯?

其实,如果不采用任何约束,Xcode会自动分配一组默认约束,即自动约束(automatic constraints),但不会在设计时生效,而是在App编译构建时进行。自Xcode 5以来,自动布局致力于让开发者专心设计界面,而不在设计过程中干扰开发者,这一点很讨人喜欢。

自动约束会为视图赋予固定的位置和大小,也就是说,和你在Storyboard中看到的坐标保持一致,这一点其实很便利,因为这样你可以在很大程度上忽略自动布局。你完全可以只为有特殊需要的部分视图创建约束,其余部分只要能行就可以保持默认,原封不动。

好,我们来玩玩看约束可以做什么。刚才拖上去的那个按钮在左上角,无约束。请确保按钮与两条辅助线对齐。

使用Editor/Pin菜单在按钮上添加两个新约束,结果像这样:

43.jpg

猜中了吗?两个约束分别是与父视图的首部间隔(Leading Space to Superview)和与父视图的顶端间隔(Top Space to Superview)。

所有约束都在Interface Builder窗口左手边的文档大纲面板中列出:

44.jpg

现在存在两个约束:一个是在按钮和主视图左边缘之间的水平间隔,还有一个是在按钮和主视图顶边缘之间的垂直间隔。这两条约束表达的关系是:

“该按钮在父视图中始终距离左上角20点。”

注:实际上在刚才的情景下这两个约束用途不大,因为显示结果和自动约束一样。上述情况下,如果你只是想让按钮始终相对父视图左上角隔一段距离,你也完全可以不添加任何约束,让Xcode使用默认的自动约束。

现在选中按钮,把它拖到场景右上角,同样吸附蓝色的辅助线:

46.jpg

卧槽,那橙色的东西是什么鬼?这个表示Interface Builder中的按钮位置大小与自动布局根据约束期望的位置大小不再保持一致,叫做视图错位(misplaced view)。

运行App,按钮依然显示在屏幕左上角:

47.jpg

对自动布局来说,橙色代表错误。Interface Builder中会显示两个橙色方框:一个虚线一个实线。虚线框表示依照自动布局的视图框架,实线框表示依照场景中布置位置的视图框架。两种应该达成一致,但这里产生了分歧。

如何修复这个问题取决于你的实际意图:

  • 想把按钮固定在距左边254点的位置?该情况下需要将现有的水平间隔常量增大234点,那个写着“+234”的橙色标章表达的就是这个意思。
  • 想把按钮改为固定在右边?该情况下需要删除现有约束并新建约束。

删除水平间隔约束。首先在面板或文档大纲中选中,然后按下键盘的delete键。

48.jpg

注意按钮周围的橙色框,还有红色点线框。橙色框告诉你布局有误,红色点线框告诉你自动布局认为在运行时该按钮会在何处显示。出现这种情况是因为计算按钮位置所需的约束不完整,你还需要为水平位置添加约束。

你也许想知道这里Xcode为何不在水平位置添加自动约束吧,原因是,仅在没有任何人为约束时,Xcode才会为其创建自动约束。也就是说,一旦你在某个视图上手动添加了一个约束,Xcode就会把该视图的布局约束设置全权交给你。此时Xcode不会为其创建任何自动约束,而是让你为这个视图添加布局所需的其他约束。

选中这个按钮,然后选择Editor/Pin/Trailing Space to Superview。这会在按钮的右边缘和屏幕右边缘之间新建一个约束,目前所有约束表达的关系如下:

“该按钮在其父视图中始终距离右上角20点。”

运行App,切换到横屏模式,注意按钮会与屏幕右边缘保持同样的距离:

50.jpg

贴靠辅助线放置一个按钮(或其他视图)并添加约束时,你会得到一个预设的间隔约束,预设标准间距的定义出自Apple官方文档《iOS人机交互指南》(iOS Human Interface Guidelines,简称HIG),对边缘附近而言,间距的标准大小是20点。

现在把按钮向左拖移一段距离:

51.jpg

橙色虚线框又出现了,视图错位嘛。假定我们想把按钮安排在这个新位置,毕竟设好约束后再让视图微调几个像素点也不是什么稀罕事,有一种解决办法是把现有约束删掉然后新建约束,但还有更快捷的办法。

Editor菜单中有一个Resolve Auto Layout Issues(化解自动布局问题)子菜单,在其中选择Update Constraints(更新约束)。在我这里呢,该命令会告知Interface Builder将相应约束增大64点,如下:

52.jpg

好,约束再次变蓝,布局有效。你可以在文档大纲中看到,水平间隔约束已经不是预设的标准间距了。

53.jpg

至今我们把水平间隔约束和垂直间隔约束摆弄了一番,接下来还有居中约束。向面板底部中心附近拖入一个新的Button对象,使其吸附辅助线:

55.jpg

想让按钮始终在水平方向上相对父视图居中,你需要添加的约束是Center X Alignment(X轴中心对齐)。从Editor菜单选择Align/Horizontal Center(对齐/水平中心),面板上会出现一条橙色长线。

56.jpg

长线显示橙色是因为你只设定了按钮的X坐标相关约束,Y坐标尚未指定。在Editor/Pin菜单中添加约束,让按钮与底边隔开一段距离,像这样:

57.jpg

选对了吗,刚刚添加的是Bottom Space to Superview约束。这条垂直间隔约束可以让按钮和父视图底边保持一定距离(默认为标准间距)。

运行App,切到横屏模式。现在在横屏模式下按钮也会位于屏幕底部的中心了:

58.jpg

之前所做就是表达“该按钮始终位于底部中心”这个意图。注意你完全不需要把按钮的具体坐标告诉Interface Builder,只需要描述你想让它定在视图中的什么地方。

自从用了自动布局,你就再也不该为安排视图的确切坐标和大小操心了,与此相对,自动布局会根据你设定的约束自动推算出合理的坐标和大小。

下图分别是自动布局启用后和之前在尺寸检查器中的不同反映,这是思维方式的转变:

59.jpg

如果不使用自动布局,在相应字段中输入X坐标,Y坐标,宽度,高度值会改变所选视图的位置和尺寸。如果使用自动布局,你依然可以在其中输入具体数值,但在视图已经拥有约束的情况下这样做会造成视图错位,需要更新约束,匹配新值。

例如,将该按钮宽度值设为100,面板会如下显示:

60.jpg

Xcode清楚表明,“想把宽度设成100点吗?没问题,但你要知道这不符合约束哦。”

这里我们假定,确实想把按钮宽度设为100点。对此有一种特殊类型的约束:Fixed Width(固定宽度)约束。首先执行撤销操作,让按钮恢复居中,所有约束呈蓝色。选中按钮,选择Editor/Pin/Width会在按钮下方新增一条“横梁”:

61.jpg

选择这条“横梁”,然后在属性检查器中设Constant为100,这会强制将该按钮宽度设定为100点,无论其中的文字大小。为方便辨认,可以为按钮设定背景色:

62.jpg

你也可以在左侧的文档大纲中看到这条宽度约束:

63.png

与其他表示该按钮与父视图空间关系的约束不同,宽度约束仅对该按钮自身有效,或许你可以视其为这个按钮和…它自己之间的约束(笑)。

你也许会问,为什么之前这个按钮没有宽度约束呢?在没有宽度约束的情况下,自动布局如何确定按钮的宽度?

真相只有一个:按钮知道自己有多宽。按钮文字附加预留空间,以此为基础计算宽度。如果你在按钮上设置了背景图片,图片也会纳入考虑。

这就是固有内容尺寸(intrinsic content size)。并不是所有控件都有这个概念,但也不少(比如UILabel也有)。如果一个视图能够自行计算尺寸,你就不必为其指定宽度约束或高度约束。后面我们还会提到。

64.png

要让按钮回到最优尺寸,请先删除宽度约束,然后选中按钮,选择Editor菜单里的Size to Fit Content(适应内容设置尺寸),恢复按钮的固有内容尺寸。

一个巴掌拍不响

辅助线不仅存在于父子视图之间,还存在于同一视图层级的不同视图之间。我们来演示一下,向面板中添加一个新按钮,然后拖动这个按钮,你会注意到,靠近其它按钮时会出现辅助线。

把新按钮拖到已有按钮附近,使其吸附辅助线:

65.jpg

这里有好几条辅助线,Interface Builder会识别两个按钮不同的对齐方式:顶端对齐、中心对齐或是底线对齐。

Xcode 4可能会把其中的辅助线吸附状态转换成一个新约束,但从Xcode 5开始,如果想在两个按钮之间设置约束,你需要手动创建,就像前面一样在Editor/Pin菜单中选择命令,其实还有一种更快捷的办法。

选中新按钮,按住control拖到另一个按钮上:

66.jpg

松开鼠标按键,会弹出一个选单,点击第一个选项,Horizontal Spacing(水平间隔)。

67.jpg

该操作会新建一个约束:

68.jpg

出现橙色方框,按钮还需要另外的约束。按钮的大小可以通过固有内容尺寸自动推断,刚刚添加的是X轴位置约束,那剩下的就是Y轴位置约束了。

在这里我们很容易断定缺少的约束是什么,但在更复杂的设计中可能并不明显。然而Xcode早已看穿一切,会明确指出缺少的约束,真是帮大忙了。

文档大纲里在View Controller Scene旁边有个小红箭头,点击箭头会列出所有的自动布局问题。

69.png

不错!缺少Y轴位置约束,我们就来补上。按住control从新按钮向下拖到容器视图上:

70.jpg

这次弹出的选单有点不一样。选单中的选项由环境决定,也就是依赖于相关视图和鼠标移动方向。选择Bottom Space to Bottom Layout Guide(与底端布局指示线的底端间隔)。(译注:Top/Bottom Layout Guide即把固有界面要素(如分页栏、工具栏、导航栏等)考虑在内的上下布局边缘,例如,当该页面下方存在分页栏时,底端布局指示线与分页栏的上边缘重合,而当页面不存在任何栏时,底端布局指示线与屏幕下边缘重合。)

现在,新按钮距屏幕底端有一定垂直间隔,与另一个按钮之间有一定水平间隔。由于空间相对较小(仅8点),“工字梁”可能有些看不清,但确实存在。

在文档大纲中点选Horizontal Space (8)约束:

71.png

这条约束存在于两个按钮之间,表达的含义是:

“无论第一个按钮在何处、有多大,第二个按钮始终显示在第一个按钮的左侧。”

选择右边的按钮,加长其中的文字,比如“A longer label”。完成后,按钮会为文本腾出空间,自动调整大小,而另一个按钮会被它挤开。毕竟设置了与第一个按钮的左边相对固定的约束。

72.jpg

你可以继续尝试,感受相关约束的工作方式。首先给较长按钮的背景设为黄色,然后再拖入一个按钮,放在黄按钮上方,使其在垂直方向上吸附辅助线(这里不要让两个按钮的左边缘对齐)。

73.jpg

为了清楚辨认新按钮的实际范围,将其背景设为绿色。

两个按钮(借助辅助线)吸附在一起,因此两者之间存在HIG建议的8点的标准间隔。按住control在按钮之间拖拽,然后在弹出的选单中选择Vertical Spacing,将标准间隔转换成约束。

控件之间并非仅限于标准间隔,约束和视图一样是货真价实的对象,也就是说约束的属性可以修改。

你可以在面板中点击两个按钮之间的“工字梁”,选中垂直间隔约束。当然这么选多少有点麻烦,最简单的方法还是在文档大纲中进行选择。选中约束后切到属性检查器:

74.png

在Constant字段输入40,修改约束大小。

运行App,切到横屏模式,观察布局效果,像之前一样添加横屏预览助理也行:

75.jpg

按钮在垂直方向上的排布得以维持,水平方向却没有。理由显而易见,绿色的按钮还没设置X轴位置约束。

对这个问题而言,添加从绿色按钮到屏幕左边缘的水平间隔约束并不妥当,这样的话在横屏模式下按钮也会保持相同的X坐标,看起来会很别扭。所以呢,我们需要表达这样的意图:

“黄色按钮始终水平居中,绿色按钮与黄色按钮左边缘对齐。”

第一个条件的约束已经设好了,现在我们来搞定第二个。Interface Builder会显示对齐辅助线,拖动上面的按钮,使其左边缘吸附黄按钮的对齐辅助线。

76.jpg

最后,按住control在两个按钮间拖拽,然后在选单中点击Left。这样创建的对齐约束表示“两个视图的左边缘始终对齐”,换句话说,两个按钮的X坐标始终保持一致。约束变蓝,布局问题解决。

77.jpg

运行App,切到横屏模式或者在预览助理中验证布局是否有效:

58.jpg

以前的“弹簧支撑杆”模型难以解决所有界面布局问题。自动布局就是用来解决这些问题的,不过由于这项技术比较强大,使用起来也对技巧性有更高要求。

幸亏Xcode 5大幅优化自动布局。如果你以前在Xcode 4尝试自动布局半途而废,请您再给Xcode 6一次机会。

这一篇我们会继续学习约束的性质和应用,结束自动布局的入门教程,

一点题外话:运行时

本教程开始构建的是如下图的简单App:

402.jpg

有两个不同背景色的按钮,这样我们可以清晰辨认具体范围。按钮之间有若干约束,各位可以从上篇构建的App继续,在面板上删掉其余两个按钮就可以了。

如果没有上篇的项目,请用Single View Application新建一个Swift iPhone应用,向场景内拖入两个按钮,设置背景色,用Editor/Pin菜单在两者之间创建Vertical Spacing约束(40点),给下面的按钮创建Bottom Space to Superview约束(20点),用Editor/Align菜单使黄色按钮在容器内水平居中,然后让两个按钮左对齐。

在Interface Builder里设置约束自然得心应手,现在我们来看看约束如何在运行时生效。在ViewController.swift中添加如下方法:

@IBAction func buttonTapped(sender: UIButton) {
  if sender.titleForState(.Normal) == "X" {
    sender.setTitle("A very long title for this button", forState: .Normal)
  } else {
    sender.setTitle("X", forState: .Normal)
  }
}

触发该事件的按钮会在长短文字间切换。在Interface Builder中按住control分别从两个按钮拖到视图控制器上,然后在弹出的选单中选择buttonTapped:,连接action方法。

运行App,点击按钮,观察按钮行为。切换横竖屏模式,再次试验。

001.png

无论哪个长哪个短,布局始终满足设置的约束:

  • 下面的按钮在窗口上始终水平居中。
  • 下面的按钮始终距离窗口下边缘20点。
  • 上面的按钮始终距离下面按钮40点,两者左对齐。

这是该用户界面完整的规范说明。

试试看,删除Leading Alignment约束(在文档大纲中选定后按delete键),然后同时选中两个按钮,在Editor/Align中选择Right Edges,运行App,观察区别。

再试试换成Align/Horizontal Centers,让两个按钮水平中心对齐。运行App,观察点击按钮后的反应。(修改约束时如果碰到橙色虚线框,记得利用Editor/Resolve Auto Layout Issues菜单的适当操作更新按钮框架。)

修复宽度

Pin菜单有个选项是Widths Equally,在两个视图间设置后会使两者等宽(取值为其中最宽的一个)。我们来玩一玩吧。

选中两个按钮,由Editor/Pin/Widths Equally添加新约束:

002.jpg

上篇中我们见过这类约束,同样以“工字梁”表示,不过中间有个等号圆圈。

虽然用两条线表示,但文档大纲中显示为一个Equal Widths约束:

003.png

修改其中一个按钮的文本,另一个的宽度也会随之改变。把下面按钮的文本改成"X",按钮会缩短,这时你会注意到上面的按钮已经无法容纳原来的文字了:

003.jpg

自动布局怎么决定以谁的宽度为准呢?观察细致的话,你会发现上面按钮出现了视图错位提示:

004.jpg

这显然不是我们想要的效果,我们选中上面的按钮,然后在Editor菜单选择Size to Fit Content(或按command-=)。现在文字已经能在按钮中完整显示了,更确切地说是按钮尺寸适应文本,然后等宽约束使得黄色按钮也随之变宽。

运行App,点击按钮,现在无论哪个按钮更长,两者都会保持等宽:

005.png

当然文字都很短的时候两个按钮也会同时缩短到相同尺寸,毕竟按钮会为完全适应内容大小而自动调节尺寸,除非另有约束规定。这叫什么来着?对,固有内容尺寸(intrinsic content size)。

在自动布局引入之前,开发者必须指定按钮和其他控件的具体大小,无论是设置frame或bounds属性还是在Interface Builder里调整尺寸,但有一个事实是大多数控件都完全能根据内容确定自己需要多少空间。

文本标签知道其中文本的长度和字体大小,故能确定自己的宽高。同理,按钮可以靠文字、背景图片和内边距综合推算自身尺寸。标签切换控件(segmented control),进度条等其他控件大多如此,不过有些控件可能只会推算自己的高度,宽度还另需设定。

这就是固有内容尺寸,自动布局中的重要概念之一。我们已经在按钮上看到具体效果了,自动布局会自动询问各个控件需要多大空间,然后根据这些信息进行布局。

利用固有内容尺寸再也通常不过,但在另有需求的个别情况下,你可以在控件上设置明确宽度约束或高度约束。

假如,你想在UIImageView上设一张图片,如果图片尺寸过大,一般会固定UIImageView的宽高,然后缩放图片,除非希望UIImageView被图片撑大。

那要是有个按钮存在固定宽度约束会怎样呢?按钮确实可以计算自身大小,但明确指定宽度后固有内容尺寸无效。选择上面的按钮,然后选择Pin/Width,按钮下方又多了一条横梁。

006.jpg

这类约束只对控件本身有效,对父视图没有影响,所以在文档大纲中归属于按钮对象。这里我们把按钮宽度定为46点:

007.png

如果你直接靠拖鼠标来拉宽按钮的话,橙色方框会接踵而至。Xcode 6不像Xcode 4那样会自动更新约束,所以在修改框架的时候还是要亲手调教约束,或者干脆直接修改约束。

选择宽度约束,在属性检查器里设Constant为80,让按钮变宽:

008.jpg

运行App,点击按钮看看如何。按钮文本确实变长了,但由于空间不足而被截断:

009.jpg

因为上面的按钮存在固定宽度约束,而两个按钮之间又存在等宽约束,按钮的尺寸始终不变。

注:不要随便给按钮设置宽度约束,默认的固有内容尺寸就是最佳选择。如果布局时发现应该改变尺寸的控件没有变化,请仔细检查是否存在误设的固定约束。

继续练练手,边做边感受,慢慢掌握固定和对齐视图的做法。记住,要确定所有视图的位置和尺寸,自动布局需要足够的约束。

010.jpg

画廊式布局示例

现在我们已经大致了解约束是什么,还有如何靠指定视图空间关系来构建布局。接下来你会看到如何利用自动布局和约束来构建一个符合实际应用情景的布局。

假设你想做一个App,在里面陈列你最喜欢的几位程序猿。横竖屏效果图如下:

011.jpg

屏幕被四等分,每块都包含一个图像视图和一个文本标签,要怎么做呢?

我们这就开始。使用Single View Application模板新建一个Swift iPhone项目,命名为“Gallery”。

打开Main.storyboard,禁用尺寸归类,因为对本教程而言Storyboard编辑器中显示iPhone比例的布局更好些。选中视图控制器,在属性检查器中选择Size为iPhone 4 inch。从对象库中拖入一个普通View对象,调整大小为宽160点、高284点,选择相对显眼的背景色(比如绿色):

012.jpg

选用普通UIView的主要原因有两个:一是我们要把它当作其他视图的容器,这样方便组织场景内容;二是将其作为自定义视图控件的占位符(placeholder),之后可以把它的Class属性设为自己的UIView或UIControl子类。

我们来为视图设定约束,之前设定约束的方法有两种:Editor/Pin和Align菜单,还有在视图间按住control进行拖拽。这里我们介绍第三种方法,在Interface Builder窗口底部有一排按钮:

013.jpg

这四个按钮都与自动布局相关。从左到右依次为:对齐(Align)、固定(Pin)、化解自动布局问题(Resolve Auto Layout Issues)、尺寸变动行为(Resizing Behavior)。前三个按钮功能和Editor菜单中的对应选项一致,第四个按钮可以指定重设视图尺寸时约束如何变化。

选中绿色视图,点击Pin按钮,弹出选单中有各种约束可供添加:

014.jpg

最上的Spacing to nearest neighbor(近邻间隔)是最常用的区域。取消选择Constrain to margins(约束以留边为准)然后点击那四条“工字梁”使其变红:

015.jpg

“留边式约束”是Xcode 6和iOS 8的一项新特性。还记得前面拖动视图时出现的辅助线吗?以前创建的间距约束都相对于父视图的真正边缘,而现在你可以创建相对于父视图“留边”的间距约束。一个视图可以定义自己的留边尺寸,使得布局更加灵活。不过本教程中依然使用传统的边缘约束。

绿色视图和父视图间新建了四个约束,分别对应上下左右,间距的具体值取决于视图放置位置,不必与本示例相同。点击Add 4 Constraints完成。

现在的Storyboard如图所示:

016.jpg

你可能会问为什么视图顶部的约束没有直通屏幕的上边缘:

017.jpg

只通到了状态栏。在iOS 7中状态栏始终绘制于视图控制器上方,再也不作为单独的栏出现。这意味着什么呢?当你创建相对于屏幕顶端的约束的时候,实际上是相对于一条看不见的线而言的,这条线叫做Top Layout Guide(顶端布局指示线)。

在常规视图控制器中,至少在状态栏没有隐藏的时候,这条指示线位于屏幕上边缘下方20点处。而在导航控制器中,指示线位于导航栏下方。因为导航栏在横竖屏模式下的高度不同,随着设备的旋转,顶端布局指示线也会移动。如此一来相对导航栏放置视图更容易了,还有一条Bottom Layout Guide(底端布局指示线)对应分页栏和工具栏。

该视图需要四条约束确定位置。与按钮或文本标签不同,一个普通的UIView视图不存在固有内容尺寸的概念,只能靠足够的约束来确定位置和大小,所以该视图需要指定尺寸的约束。

尺寸约束在哪里?这里我们可以根据父视图尺寸推断该视图的大小。布局中存在两条水平间隔约束和两条垂直间隔约束,四条约束均为定长,可以在文档大纲中看到:

018.png

绿色视图的宽度公式为“父视图宽度 - ( 54 + 74 )”,高度公式为“父视图高度 - ( 50 + 214 )”。而间隔约束均为定长,所以视图大小已经确定下来了。(此处具体数值可能不同。)

切换横竖屏时,父视图尺寸变化(宽高交换),公式也随之改变。

这一点可以靠运行App切换横屏模式验证,也可以直接在Interface Builder中模拟。点击Editor/Simulated Screen,选择一个iPhone横屏,你会看到App的横屏效果:

019.jpg

你可能并不希望UIView在设备旋转时调整大小,我们这就利用约束为视图指定明确宽高。选中绿色视图,点击Pin按钮,在弹出的选单中令Width和Height处于被选状态。

020.jpg

点击Add 2 Constraints完成。现在向视图添加了两条约束,一个是160点宽度约束,另一个是284点高度约束:

021.jpg

宽高约束因为只对该视图有效,在文档大纲中归属于绿色视图本身。通常约束用来表达两个不同视图之间的几何关系,比如水平间隔约束和垂直间隔约束存在于绿色视图和父视图之间,但你可以将宽度约束和高度约束视为绿色视图和自身之间的关系。

运行App,哦,竖屏看起来不错。切到横屏,哎呀!不只是看起来不对劲(视图尺寸又变了),Xcode调试面板中还出现了蛋疼的报错信息:

Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this: (1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand,
refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"
)
Will attempt to recover by breaking constraint....

还记得吗,为使自动布局计算所有视图的位置和大小,我们需要设定足够的约束。这次的问题恰恰是“约束过多”,报错信息“Unable to simultaneously satisfy constraints(无法同时满足多条约束)”表示存在约束冲突。

我们再来审视一下这些约束:

022.jpg

绿色视图共设有六条约束,前面四条间隔约束,还有新增的宽度约束和高度约束。哪里有冲突呢?

竖屏模式下没有问题,因为计算结果正好合适。水平间距和宽度约束加起来正好是父视图宽度,垂直方向上也一样。

但设备转成横屏时,结果出现分歧,自动布局不知如何是好。

对于此处的冲突,我们可以认为视图宽度固定但边距必须变化,或者是边距固定但宽度必须变化。所谓二者不可得兼,舍其一而取其余者也。本例中,我们希望视图在横竖屏模式下宽度相同,所以必须舍去水平方向上的间隔约束。

删除右侧的水平间隔约束和下侧的垂直间隔约束,Storyboard如图所示:

023.jpg

现在视图的约束恰好可以确定其位置大小,不多不少刚刚好。运行App验证是否存在报错信息,旋转前后视图宽度是否保持一致。

就算Interface Builder尽力提醒布局是否有效,它也不是全能的。缺少约束时会提示,但目前对约束过多的情况束手无策。

不过至少在运行时,自动布局会在必要时给出详细的报错信息。为了解如何分析这类报错信息并诊断布局问题,请参阅iOS 6 教程的“中级自动布局”。

显示画像

向绿色视图中拖入一个Label文本标签,注意其中出现的辅助线,因为绿色视图会作为文本标签的父视图。

44.jpg

利用辅助线,把文本标签放在水平居中并紧贴底部留边的地方。在文本标签上添加距绿色视图底边20点的底端间隔约束,最快的方式是利用Pin按钮,选择底部的梁:

45.png

现在添加水平居中约束。之前我们一直在用Editor/Align菜单,你也可以用Align按钮来搞定。选中文本标签,点击Align按钮,弹出选单:

024.png

选中Horizontal Center in Container,点击Add 1 Constraint,此时Storyboard应如图所示:

025.png

注意新加的这两条约束都列在绿色视图之下,而不是主视图。

向Storyboard中拖入一个Image View对象,如图布局:

026.jpg

该图像视图的上、左、右边缘都相对于父视图设有固定间距,不过下边距(相对于文本标签上边)为8点标准间距。如果没有足够把握,请遵循以下步骤:

1. 不要在意具体大小和位置,把Image View拖入绿色视图:

027.jpg

2. 选中图像视图,点击Pin按钮,选择以下选项:

028.png

上、左、右边距均设为20点,下边距设为8点。最重要的一点:对于Update Frames,你应该选择Items of New Constraints,如果忘记设定(默认为None),Storyboard会变成这个样子:

029.png

默认选项会导致设定的约束与图像视图的实际位置大小不同,但选择Items of New Constraints的话,Interface Builder会在添加约束时自动调整视图框架,干净利落:

030.png

当然,如果出现视图框架错位,可以用Resolve Auto Layout Issues按钮来修复:

031.png

添加图片

接下来下载教程资源,解压后将其中的Images文件夹添加到项目中,把Ray.png设为图像视图的图片,将其模式修改为Aspect Fit(局部适应,即把整张图片按比例缩放,在视图中呈现全貌),设背景色为白色。将标签的文本修改为“Ray”。

布局如图所示:

032.jpg

注意在设定图片时,绿色视图内的约束变为橙色。为什么突然就布局无效了呢?在Xcode中查看错误之前我们可以先猜一猜。

点击文档大纲中View Controller Scene右侧的红色箭头图标,检查问题:

033.png

报错为Content Priority Ambiguity(内容优先级不明确),这个叫法有些晦涩,表达的意思是:如果图像视图和文本标签都没有固定高度,那么当绿色视图高度变化时,自动布局不清楚各个子视图要分别缩放多少,也就是说,不清楚按什么比例分配缩放空间。(目前的Interface Builder似乎会忽视绿色视图上存在的固定高度约束。)

我们再来具体地讲解一遍,假设某时刻绿色视图高度增加了100点,那么自动布局会如何在文本标签和图像视图两者间分配这新加的100点高度呢?是文本标签原封不动,图像视图增高100点?还是图像视图不动,文本标签增高?是两者分别增高50点,还是三七开、四六开?还是其他可行方案?

如果不指明,自动布局只能自行揣度,结果可能很难预测。

正确的使用姿势是修改文本标签的Content Compression Resistance Priority(内容抗压优先级),顾名思义,该值决定视图的抗压缩属性,抗压优先级高意味着视图更倾向于保持原尺寸,降低被缩小的可能性。

与此相对,Content Hugging Priority(内容贴合优先级)决定视图的抗膨胀属性。此处的“贴合”可理解为“刚好合适的尺寸”,即视图边缘贴合内容,或接近固有内容尺寸。贴合优先级高意味着视图更倾向于保持原尺寸,降低被扩张的可能性。

在文本标签的尺寸检查器中设Content Compression Resistance Priority / Vertical为751,高于图像视图的优先级。同时设Content Hugging Priority / Vertical为252。当父视图尺寸变化时,随之改变的是图像视图,而文本标签会保持原样。

034.png

约束再次变蓝,自动布局警告一扫而空。

照葫芦画瓢

把绿色视图拖到主视图的左上角,不过我们之前在上面设置了水平间隔约束和垂直间隔约束,拖动会造成视图布局错位。

035.jpg

Resolve Auto Layout Issues按钮派上用场了,点击并选择Update Constraints。之前我们选过的Update Frames的作用是依照约束规定调整视图框架,而这次正好相反:依照视图框架更新约束信息。

注意顶端垂直间距现在变成了负数,这是因为该约束的参照物是顶端布局指示线。约束值出现负数在道理上也讲得通,所以此处无需干预。(如果您有重度强迫症,请删除那个带负数的约束,然后参照父视图顶端重新设置约束。)

水平间隔现在为0,由左边缘的蓝线表示。视图已经安放在左上角,但真正把它定住的还是约束:

036.png

选中绿色视图,按下command-D克隆视图,将复件放到右上角:

037.png

注意出现橙色,复制视图时自然不会保留XY坐标约束。为此,我们要设置约束,把视图复件固定在右上角。

再复制两次,分别放到左下角和右下角,依然设置边缘约束。

页面设计如下:

038.png

欢迎来到国家动物园程序猿馆!(`?ω?′)

运行App,竖屏还不错,横屏有问题了:

039.png

显然,问题在于四个视图都设定了固定宽高约束,无论父视图有多大,尺寸始终保持不变。

再文档大纲中选中并删除四个视图的所有固定宽高约束。现在运行App会变成这样:

040.png

注:这里四个视图大小不一致的原因依然是固有内容尺寸,图片尺寸决定图像视图大小,文本尺寸决定文本标签大小,加上周围的20点边距,放到一起决定各个视图的大小。

有种上篇问题的即视感,还记得当时是如何解决的吗?对,等宽约束和等高约束。

在文档大纲中按住command分别选中四个视图,在Pin按钮中选中Equal Widths和Equal Heights,点击Add 6 Constraints,批量添加约束。

041.png

运行App,横屏,嗯…还是不大对:

042.png

所有视图确实满足等宽等高的约束,但具体的宽高数值与预期不符。

也就是所有视图等宽等高不足以确定实际大小,因为自动布局不清楚四个视图之间的空间关系。虽然并排放置,但其间不存在约束,自动布局不知道你的意图是把画面四等分。

自动布局不明白,我们就要讲清楚。

043.png

选中上面的两个视图,选择Editor/Pin/Horizontal Spacing,由于两个视图并列排放,该操作会在两个视图之间添加一条水平间隔约束,间距为0,同样在左侧两个视图之间设置垂直间隔约束,这样就讲清楚了。

运行App,最终效果如下:

044.png

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文