作为一名刚刚入门 iOS 开发的人来说,理解并熟练使用 Auto Layhout 非常重要。开始接触自动布局是非常困难和复杂的,布局一个视图通常需要添加非常多的约束constraints,尤其是在创建动态视图时,我们要不断的重复添加和删除约束以达到我们想要的效果。而使用 Stack View 可以很大程度上简化我们频繁调试约束的过程。

通常,我们在创建用户界面的时候很多视图是程线性排列的。以下面这个界面为例,界面中包括非常多的子视图UILabel,它们是垂直vertically线性排列的。

示例

当用户界面像上图这样程线性排列的时候,就可以考虑使用Stack View了,它是一个UIStackView的实例,用来创建垂直或水平的布局。不仅简单实现布局,而且可以帮助你管理不同 View 的各种约束。在理想状态下,你还可以将另外一个 StackView 嵌入到 StackView 中,得益于这个特性,我们可以极大的缩减在布局用户界面时花费的时间。

接下来,我们将创建一个对用某个 model 元素的 Detail 视图来了解一些关于Stack View的特性。
首先,创建一个新的项目,从库中拖拽一个Vertical Stack View 到 Storyboard 中默认的 ViewController 中。

为其添加 leading 和 trailing margins 的约束,以及 top 和 bottom 的约束,边距可以设置为 8 point。让它充分填满屏幕。

拖拽四个 UILabel 到刚刚创建的 StackView 中。

你会发现所有的 Label 四周都被标记红色辅助线(在 Auto Layout 中的错误提示),在这里意味着这些 Label 存在不明朗的垂直布局。我们有两种方式解决这个问题,既可以使用 Auto Layuout,也可以通过修改 StackView 的一些属性。这里我们先尝试使用 Auto Layout 来解决这个问题,同时方便理解一些用好 Auto Layout 必备的知识点。

解决这个问题我们先要理解一个基本概念,叫做intrinsic content Size , 如果你没有指定某一个子视图的 width 和 height 的约束,那么这个视图将通过自身内容的大小来决定自己的大小。比如一个 UILabel, 当它需要被绘制在屏幕上时发现自己没有 width 和 height 的约束,那它将通过自己的字体大小,text 长度等属性自行判断自己的宽和高。但这会带来一个问题。试想如下图的这种状况:

两个水平对齐的 UILabel

如果这两个 Label 的 superView 发生变化,哪个 Label 会变得更宽?第一个?第二个? 或者两个一起?

这个时候我们就需要使用每一个 View 都拥有的 content hugging prioritiescontent compression resistance priorities 来决定视图被拉伸或压缩的优先级。

  • 其包括下面四种优先级属性:
  • horizontal content hugging priority
  • vertical content hugging priority
  • horizontal content compression resistance priority
  • vertical content compression resistance priority

对于上面水平对齐两个 Label, 如果其中一个 Label 的 horizontal content hugging priority 优先级更高,更不容易被拉伸,你可以想象优先级更高的 Label 内部有一种力量,像是一个皮筋,从两端向中心发力。在 superView 变宽的时候强迫自己 hold 住现在的身段。

Screen Shot 2016-03-17 at 4.22.11 P

现在你已经对 content hugging priority 有了简单的了解,我们现在来思考一下 content compression resistance priority 的作用,它决定了一个 View 有多大的力量抵抗被压扁。

同样是上面两个水平对齐的 Label。这次的场景不是 superView 变宽,而是变窄了,哪个 Label 会被压缩?

Screen Shot 2016-03-17 at 4.32.10 P

如果其中一个 Label 拥有更高的 content compression resistance priority 优先级,那么它将更能抵抗住被压扁,对于 Label 来说,更不容易出现上图那样文字显示不全的情况。

好啦,针对 View 的 模糊约束介绍到这里,有了这个概念,解决最开始 四个 Lable 的问题就变的很简单了。
我们选择Date Created label 打开它的 size inspector 界面,也可以使用快捷键 option + cmd + 5 在这里就可以看到关于这个 view 的压缩和拉伸优先级设置的界面了,这里 xcode 已经设置了相同的默认数值。

Screen Shot 2016-03-17 at 4.46.46 P

我们修改 Vertical Content Hugging Priority 值为 249, 这样,它上面的另外三个 label 就拥有了同样且更高的优先级,意味着它们三个更有力量保持自己本身的高度,而 Data Created label 因为自己的力量不够,会被用来填充剩余的屏幕空间。

修改之后,视图布局自动变成下图的状态。
Screen Shot 2016-03-17 at 4.51.48 P

Ok,问题解决了,接下来我们来看看如何利用 Stack View 的特性来解决同样的问题,其实 stack view 拥有一个属性来决定它的子视图的布局样式。

从 storyboard 中选择我们的 stack view(可以在 ViewController 上使用快捷键 shift + cmd + 鼠标右键 来选中)打开它的 attributes inspector 界面,找到最顶部的 Stack View 的 section 中的 Distribution 属性。

Screen Shot 2016-03-17 at 5.03.01 P

默认的布局方式为 Fill,它会让其所有的子视图按照其自身的内容大小来决定布局的样式。我们修改它的值为 *Fill Equally ,从结果就可以看到,四个 Label 拥有了一样的高度,Fill Equally 会忽略其子视图的自身内容大小来屏幕划分区域

Screen Shot 2016-03-17 at 5.06.52 P

我们重新 Distribution 属性为 Fill, 这是接下面我们要使用的布局方式。

对于 Stack View 来说,最重要的特性就是嵌套,一个 stack view 可以被加入到另外一个 stack view, 在创建复杂的用户界面时,这样的特性非常实用。接下来我们继续丰富我们的界面。

在最顶部的三个 UILabel 的右边都有一个 TextField,我们先完成第一个 Name Label。首先选择 Name Label, 选择在 canvas 中右下角的 Auto Layout constraints 菜单中最左边的选项。它会将你选中的视图嵌入到一个新的 stack view。

Screen Shot 2016-03-17 at 5.13.25 P

选择新的 stack view, 打开它的 attributes inspector 界面,其默认的 Axis 布局方向是 vertical, 我们修改为 horizontal

从库中拖拽一个 Text FieldName Label 的右边。
Screen Shot 2016-03-17 at 5.18.39 P
默认设置下,Name Label 拥有更高的 Horizontal content hugging priority 所以,你会看到 Name Label 保持住了自己的宽度,而 text field 被用来填充剩余的空间。而它们的 content compression resistance priority 默认情况下是相同的,这会导致在屏幕宽度变窄的时候,Name Label 仅有的小身板被压缩,我们不希这种情况发生。所以我们要降低 text field 的 Horizontal content compression resistance priority 值,修改为 749。 让 Name Label 有更大的力量抵抗被压扁。

接下来,我们希望 name label 和 text field 之间有些间隔,这样看起来会更舒服。 Stack view 可以自定义其中子视图的间隔。

选择 name label 和 text field 所在的 stack view, 打开 attributes inspector,修改其中的 Spacing 为 8 points。注意 text field 会自动缩短,这是因为它抵抗被压缩的能力更低。

接下来,我们将 serial 和 value label 也用同样的方法为期配对 text field。
Screen Shot 2016-03-17 at 5.47.57 P

添加完成后我们仍然需要进行一些微调,首先我们需要让垂直布局的 stack view 也拥有一些间距,并且三个 label 对应的 text filed 并没有对齐。

选择垂直的 stack view, 也就是我们最开始创建的那个,修改其 Spacing 为 8 point。 选择 Date Created label, 修改其文本排列样式为居中。

接下来是 text field 没有对齐的问题了,其实,stack view 实质上的作用就是用来减少 constraints 的的设置,但某些 constraints 仍然很重要。造成没有对齐的原因就是 text field 前面的 label 的宽度并不一致,而间隔我们设置为同样的数值 8。解决这个问题,我们需要在三个 text field 上添加 leading edge 的约束。

按住 Ctrl 从 Name text field 拖拽到 Serial text field 选择 Leading, 然后对 Serial text field 和 Value text field 做同样的约束添加。 It’s Done!

Screen Shot 2016-03-17 at 5.57.15 P

Stack view 允许你创建非常复杂的用户界面同时又非常便捷高效,不要忘记很多时候 contraints 仍然需要添加,但更多的约束是有 stack view 来管理,而不是你自己。Stack view 还允许你创建动态的用户界面。你可以使用 addArrangedSubView(_:insertArrangedSubview(_:atIndex:)removeArrangedSubview(_:atIndex:) 在 stack view 中添加或者删除某个 view。也可以设置 view 的 hidden 属性 让某个 view 出现和消失,stack view 会识别这个状态,并自动重新布局。