iOS Auto Layout-栈视图(译)
下面的内容主要介绍了如何使用 stack view 创建一些复杂的布局。Stack view 是一个很强悍的工具,使
用它设计用户界面会十分便捷。Stack view 的一些属性在很大程度上只能控制它的子视图如何排列。你可以通过增加一些额外的自定义约束,来更加精确地控制子视图的排列方式;然而,那样将会使布局变得十分复杂。
有关于这部分内容的源码,请查看 Auto Layout Cookbook 这个项目。
简单的 Stack View
这里用了一个竖直排版的 stack view 对一个 label、一个 image view 和一个 button 进行了布局。
![](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Simple_Stack_View_Screenshot_2x.png)
视图和约束
在 Interface Builder 上,拖拽一个垂直方向排布的 stack view,然后在上面添加 flowers label、image view 和 edit button。然后按照下面的方式设置约束条件。
![](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/simple_stack_2x.png)
1 | 1. Stack View.Leading = Superview.LeadingMargin |
属性
在属性检查器中,设置以下属性:
Stack | Axis | Alignment | Distribution | Spacing |
---|---|---|---|---|
Stack View | Vertical | Fill | Fill | 8 |
下一步,给 Image View 设置以下属性:
View | Attribute | Value |
---|---|---|
Image View | Image | (一张花的图片) |
Image View | Mode | Aspect Fit |
最后,在 Size 检查器中,设置 Image View 的 content-hugging 和 compression-resistance (CHCR) 权重。
Name | Horizontal hugging | Vertical Hugging | Horizontal resistance | Vertical resistance |
---|---|---|---|---|
Image View | 250 | 249 | 750 | 749 |
讨论
你需要通过设置约束,将 stack view 固定在 superview 上,另一方面,你还需要处理 stack view 的内部布局逻辑。
在上图中,stack view 以一个标准边距充满了整个父视图。Stack View 中的子视图通过调整充满整个 stack view 的边缘。水平方向上,每个视图通过拉伸以适应 stack view 的宽度。竖直方向上,view 按照之前设置的 CHCR 权重来进行拉伸。Image View 应该按照预留空间的大小进行适配。因此,在竖直方向上,image view 的 CHCR 权重应该要低于 label 和 button 的默认权重。
最后,设置 image view 的 mode 为 Aspect Fit。这个设置会强制 image 去调整比例以适应 image view 的大小,以防止 image 因为 image view 的改变而比例失调。这样设置可以允许 stack view 随意改变大小,不用担心图片变形。
关于如何通过布局使一个 view 充满 superview 的更多内容,请查看 Attributes 和 Adaptive Single View。
嵌套 Stack Views
这部分内容讲述了一个由多重嵌套的 stack view 构建成的一个复杂的布局。但是,在下面这个布局示例中,并不是只用 stack view 就能创建的,还需要一些额外的约束条件,更加精确地控制布局。
![](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Nested_Stack_Views_Screenshot_2x.png)
添加完视图层级之后,如果添加约束将在下一小节讲解。
视图和约束
当处理一个嵌套 stack view 布局时,比较容易的做法是从里向外布局。例如在下图中,先在 Interface Builder 布局 “姓名” 这一行。将 label 和 text field 并排放在一起,然后选中它们两个,点击 Editor > Embed In > Stack View 菜单项。这将为这一行创建一个水平布局的 stack view。
然后,布局剩下的 “姓名” 相关的两行,选中,然后点击 Editor > Embed In > Stack View 菜单项,将会创建另外两个水平布局的 stack view。类似的,完成如下显示的一个布局。
![](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/nested_stack_views_2x.png)
1 | 1. Root Stack View.Leading = Superview.LeadingMargin |
属性
每个 stack view 有一系列它们自己的属性,这些属性定义了 stack view 里的内容如何排布。在属性检查器中,你可以看到如下属性:
Stack | Axis | Alignment | Distribution | Spacing |
---|---|---|---|---|
First Name | Horizontal | First Baseline | Fill | 8 |
Middle Name | Horizontal | First Baseline | Fill | 8 |
Last Name | Horizontal | First Baseline | Fill | 8 |
Name Rows | Vertical | Fill | Fill | 8 |
Upper | Horizontal | Fill | Fill | 8 |
Button | Horizontal | Fist Baseline | Fill Equally | 8 |
Root | Vertical | Fill | Fill | 8 |
除此之外,在布局中设置 text view 的背景色为亮灰色。这样当布局发生变化时,你可以很容易看到 text view 的具体大小。
View | Attribute | Value |
---|---|---|
Text View | Background | Light Gray Color |
最后,CHCR 的权重决定了在填充剩余空间时哪个 view 应该被拉伸。在 Size 检查器中,你可是看到如下每个 view CHCR 的权重:
Name | Horizontal | Vertical hugging | Horizontal resistance | Vertical resistance |
---|---|---|---|---|
Image View | 250 | 250 | 48 | 48 |
Text View | 250 | 249 | 250 | 250 |
First,Middle,and Last Name Labels | 251 | 251 | 750 | 750 |
First,Middle,and Last Text Fields | 48 | 250 | 749 | 750 |
讨论
在这部分内容中,多个 stack view 互相作用,共同完成了一个复杂的布局。但是,这些 stack view 并不能独立完成所有的布局效果。例如,在一个 image view 改变大小时,里面的 image 应该保持纵横比不变。不幸的是,在 简单的 Stack View 这部分内容用的的技术并没有用到这里。在这里,image 需要紧贴 image view 头部和底部边缘。如果将 image view 的 mode 设置为 Aspect Fit 可能会导致上下留白。幸运的是,在这里 image 的纵横比永远保持为正方形,所以你可以让 image 完全充满 image view,然后约束 image view 的纵横比为 1:1。
备注
在 Interface Builder 中,一个纵横比的约束是一个 view 的高度和宽度约束的合成。Interface Builder 可以以多种方式去显示多条约束。一般情况下,对于纵横比约束,会以一个比例式的形式展示。例如 一个
View.Width = View.Height
约束代表一个 1:1 的长宽比约束。
除此之外,所有的 text fields 宽度应该相同。不幸的是,他们被分布在不同的 stack view 中,这样就不能通过 stack view 进行处理。因此,你必须对这些 view 添加 equal width 约束。
和其他简单的 stack view 一样,你需要改变其中一些 stack view 的 CHCR 属性。以此来定义当父视图的大小发生改变时,view 应该如何压缩或扩展。
竖直方向上,你想让 text view 充满整个 stack view。因此在进行设置时,text view 的 Vertical Hugging 权重一定要低于其他 view。
水平方向上,Label 的大小一般为固有内容的大小。当 text field 进行填充适配时,默认的 CHCR 权重设置可以使 label 不会受到挤压。虽然 Interface Builder 早已经将 label 的 content hugging 权重设置为 251,使它要高于 text fields;但是,你仍然需要将 text fields 的 CHCR 权重设置的更低。
为了使 image view 可以和 name row stack view 一样高,需要适当对其进行压缩。然而,stack view 为了让内容能显示出来,只会尽可能地扩大空间。这意味着一定要将 image view 的 vertical compression resistance 设置的非常低,这样 image view 才会压缩而不是充满 stack view。除此之外,设置 image view 的纵横比之后,会使布局变得十分复杂,因为设置纵横比之后,水平和竖直方向将会相互作用。这意味着还需要将 text fields 的 horizontal content hugging 权重设置一个较低值,以此避免 image view 被压缩。综合考虑,将权重设置为 48 或者更低最为合适。
动态 Stack View
这部分内容展示了如何在运行时动态插入或删除一条 item。所有的变化以动画形式展示。除此之外,图中的 stack view 加在了一个 scroll view 内部,如果一屏幕展示不开,可以进行滚动。
![](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Dynamic_Stack_View_Screenshot_2x.png)
备注
这部分内容意在介绍如何动态操作 stack view,以及如何在 scroll view 中使用 stack view。在真实的 APP 开发中,这种场景一般使用 UITableView。一般情况下,你不应该使用动态 stack view 来代替 table view。并且,使用这种方式创建的界面不能灵活使用其他的布局技巧。
视图和约束
一开始的界面十分简单。在画布上放置一个 scroll view 并使其充满画布。然后,在 scroll view 中放置一个 stack view,并且在 stack view 中放置一个 button。所有控件放置好之后,设置如下约束:
![](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/Art/dynamic_stack_view_2x.png)
1 | 1. Scroll View.Leading = Superview.LeadingMargin |
属性
在 Attributes 检查器中,给 stack view 设置如下属性:
Stack | Axis | Alignment | Distribution | Spacing
Stack View | Vertical | Fill | Equal Spacing | 0
代码
这里会通过使用一些代码进行布局。创建一个自定义的 view controller,然后将 scroll view 和 stack view 以 outlets 的方式引入。
1 | class DynamicStackViewController: UIViewController { |
接下来,重写 viewDidLoad 方法,在其中设置 scroll view 的位置。如果你想让 scroll view 的内容开始于 status bar 的下面,按照下面代码设置:
1 | override func viewDidLoad() { |
现在,为 button 添加一个 action。
1 | // MARK: Action Methods |
这个方法首先计算了 scroll view 的 offset,然后创建了一个新视图。将新视图设置为隐藏并加入 stack view。被隐藏的视图不会影响 stack view 的显示和布局——所以 stack view 的显示效果保持不变。然后,在一个动画的 block 中,设置 view 的显示并更新 scroll view 的 offset,使 view 以动画形式展示出来。
类似的,添加一个删除视图的方法。但是,与 addEntry
方法不同,这个方法不会直接关联 Interface Builder 上的任何控件。而是在 view 创建时,以编码的方式关联上每个 view。
1 | func deleteStackView(sender: UIButton) { |
这个方法里,在 animation 的 block 中隐藏 view。完成动画之后,将 view 从 view 层级中移除。这样就可以使 view 自动从 stack view 中移除。
添加到 stack view 中的条目可以是任意样式,在这个例子中,每个条目是一个 stack view,这个 stack view 中包含了一个显示日期的 label,一个显示十六进制字符串的 label,一个删除 button。
1 | // MARK: - Private Methods |
讨论
在这个样例中,stack view 可以在 APP 运行时动态添加或删除 view。然后 stack view 可以根据内容变化动态改变布局。最后,这里有一些重要的点值得我们记住:
- 隐藏的 view 一直存在于 stack view 的子 view 数组中. 然而,它们不会展示,也不会影响布局和其他子 view.
- 将一个 view 加到 stack view 的子 view 数组中时,这个 view 会自动添加到 view 层级.
- 将一个 view 从 stack 的子 view 数组中移除时,不会从 view 层级中自动移除;将一个 view 从 view 层级中移除,同样也不会从 stack 的子 view 数组中移除.
- 在 iOS 系统中,view 的 hidden 属性通常情况下么有动画效果.然而在这里,将 view 放到 stack view 的子 view 里面时会有动画效果,这个效果是 stack view 实现的,而不是 view 通过使用
hidden
属性实现的.
这部分内容同时还简单介绍了如何在 scroll view 中使用自动布局。这里在 stack view 和 scroll view 之间设置了一系列的约束,以此来定义 scroll view 内容区域大小。在水平方向上,设置 stack view 的宽度充满 scroll view。竖直方向上,scroll view 的 content size 由 stack view 的大小来决定。stack view 会随着加入的条目越多而变得越长。相应的,scroll view 滚动区域也会随之增加,以适应 stack view 的内容大小。
关于更多信息,请查看 Working with Scroll Views.
![](/uploads/Reward.png)