翻譯@Auto Layout Guide(自動(dòng)布局指南)
- 原文:Auto Layout Guide
- 作者:Apple
- 更新:Yannmm@Github.com
Auto Layout Cookbook(自動(dòng)布局使用手冊(cè))
Stack Views(利用堆疊視圖布局)
本節(jié)通過(guò)實(shí)際例子展示堆疊視圖的使用,界面復(fù)雜程度不斷上升。堆疊視圖能夠有效降低界面復(fù)雜度,加快搭建速度;通過(guò)調(diào)整其各項(xiàng)屬性,做到精細(xì)布局。此外,還可以在此基礎(chǔ)上進(jìn)一步添加約束,實(shí)現(xiàn)預(yù)期效果;然而布局復(fù)雜度也會(huì)因此上升。
具體源碼詳見(jiàn)項(xiàng)目Auto Layout Cookbook。
Simple Stack View(簡(jiǎn)單堆疊視圖)
本例中,我們利用堆疊視圖垂直布局一個(gè)標(biāo)簽(label),一個(gè)圖像視圖(image view)以及一個(gè)按鈕(button)。

Views and Constraints(搭建布局)
拖拽一個(gè)堆疊視圖(stack view)到畫(huà)布上;向其中添加一個(gè)標(biāo)簽,一個(gè)圖像視圖和一個(gè)按鈕。隨后按照下圖添加約束:

Stack View.Leading = Superview.LeadingMargin
Stack View.Trailing = Superview.TrailingMargin
Stack View.Top = Top Layout Guide.Bottom + Standard
Bottom Layout Guide.Top = Stack View.Bottom + Standard
Attributes(設(shè)置屬性)
打開(kāi)堆疊視圖屬性面板,設(shè)置如下:
| Stack | Axis | Alignment | Distribution | Spacing |
|---|---|---|---|---|
| Stack View | Vertical | Fill | Fill | 8 |
然后打開(kāi)圖像視圖屬性面板,設(shè)置如下:
| View | Attribute | Value |
|---|---|---|
| Image View | Image | an image of flowers(一張鮮花圖片) |
| Image View | Mode | Aspect Fit |
最后,打開(kāi)圖像視圖尺寸面板,調(diào)整外擴(kuò)和內(nèi)縮優(yōu)先級(jí):
| Name | Horizontal hugging | Vertical hugging | Horizontal resistance | Vertical resistance |
|---|---|---|---|---|
| Image View | 250 | 249 | 750 | 749 |
Discussion(分析&討論)
系統(tǒng)根據(jù)堆疊視圖的內(nèi)容計(jì)算其尺寸。所以,只需固定其位置即可。
這里我們讓堆疊視圖填充父視圖,四周標(biāo)準(zhǔn)間距。內(nèi)容視圖尺寸經(jīng)過(guò)縮放,以適應(yīng)堆疊視圖:水平方向上,拉伸至堆疊視圖寬度;垂直方向上,根據(jù)各自的內(nèi)縮和外擴(kuò)優(yōu)先級(jí)調(diào)整高度。圖像視圖應(yīng)該被優(yōu)先縮放,因此其垂直方向上的外擴(kuò)和內(nèi)縮優(yōu)先級(jí)應(yīng)該小于其他內(nèi)容視圖。
隨后將圖像視圖的內(nèi)容模式(content mode)設(shè)置為等比例縮放適配(Aspect Fit)。顧名思義,圖像被等比例縮放以適應(yīng)視圖尺寸。因此,無(wú)論視圖尺寸如何變化,圖像也不會(huì)變形。
更多關(guān)于固定子視圖以填充父視圖的信息,詳見(jiàn)章節(jié)Simple Single View(簡(jiǎn)單獨(dú)立視圖)以及Adaptive Single View(自適應(yīng)獨(dú)立視圖)。
Nested Stack View(嵌套堆疊視圖)
這次我們通過(guò)嵌套多個(gè)堆疊視圖實(shí)現(xiàn)一個(gè)復(fù)雜布局。另外為了達(dá)到預(yù)期效果,還需進(jìn)一步添加約束。

首先構(gòu)建視圖結(jié)構(gòu),再添加約束,如下一章節(jié)Views and Constraints(搭建布局)所示。
Views and Constraints(搭建布局)
處理層層嵌套的堆疊視圖時(shí),遵循"由內(nèi)而外"原則。從姓名部分的一行開(kāi)始:并排擺放一個(gè)標(biāo)簽和一個(gè)文本框,同時(shí)選中,依次點(diǎn)選菜單欄Editor > Embed In > Stack View。最后生成一個(gè)水平堆疊視圖,可以作為姓名部分的一行使用。
創(chuàng)建三行,同時(shí)選中,依次點(diǎn)選菜單欄Editor > Embed In > Stack View。我們獲得了一個(gè)垂直堆疊視圖,可以作為姓名部分使用。不斷重復(fù),按照下圖搭建界面,添加約束。

Root Stack View.Leading = Superview.LeadingMargin
Root Stack View.Trailing = Superview.TrailingMargin
Root Stack View.Top = Top Layout Guide.Bottom + 20.0
Bottom Layout Guide.Top = Root Stack View.Bottom + 20.0
Image View.Height = Image View.Width
Attributes(設(shè)置屬性)
每個(gè)堆疊視圖擁有自己的屬性,影響內(nèi)容布局。打開(kāi)屬性面板,設(shè)置如下:
| 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 | First Baseline | Fill Equally | 8 |
| Root | Vertical | Fill | Fill | 8 |
另外,將文本框背景顏色設(shè)置為淺灰色(light gray)。便于我們?cè)谠O(shè)備方向改變時(shí),觀察其尺寸變化。
| View | Attribute | Value |
|---|---|---|
| Text View | Background | Light Gray Color |
外擴(kuò)和內(nèi)縮優(yōu)先級(jí)決定視圖的拉伸順序。打開(kāi)尺寸面板,設(shè)置如下:
| Name | Horizontal 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 Name Text Fields | 48 | 250 | 749 | 750 |
Discussion(分析&討論)
本布局幾乎全部通過(guò)堆疊視圖完成??上В⒎侨?。例如圖片應(yīng)始終保持原始比例。然而我們無(wú)法使用章節(jié)Simple Stack View(簡(jiǎn)單堆疊視圖)中的技巧。圖片四周間距必須保持不變,設(shè)置內(nèi)容模式為"等比例縮放適配(Aspect Fit)"會(huì)引入間隙。幸運(yùn)的是,這里圖片寬高比始終為1:1,所以將視圖寬高比約束為1:1即可。
注意
對(duì)于IB來(lái)說(shuō),比例約束(Aspect Ratio)就是視圖寬高之間的約束。約束中系數(shù)(Multiplier)有多種含義。對(duì)于比例約束來(lái)說(shuō),如果
View.Width = View.Height,則系數(shù)為1,表示1:1等比例約束。
此外,所有文本框都應(yīng)等寬。然而,它們散落在各個(gè)堆疊視圖中,無(wú)法統(tǒng)一管理。所以,需要添加等寬約束。
同簡(jiǎn)單堆疊視圖一樣,必須調(diào)整部分視圖的外擴(kuò)和內(nèi)縮(CHCR)優(yōu)先級(jí),以便其尺寸隨父視圖變化。
垂直方向上,上部堆疊視圖(Upper Stack)和按鈕堆疊視圖(Button Stack)之間的空隙需要由文本視圖填充。因此,其內(nèi)縮優(yōu)先級(jí)最低。
水平方向上,標(biāo)簽保持其固有寬度;文本框填充剩余空間。前者的優(yōu)先級(jí)無(wú)需修改,因?yàn)镮B會(huì)自動(dòng)將其內(nèi)縮優(yōu)先級(jí)設(shè)置為251,高于后者;然而,我們?nèi)孕柚鲃?dòng)降低將文本框的外擴(kuò)和內(nèi)縮優(yōu)先級(jí)。(譯者:最后一句我也不知道為什么??)
圖片高度應(yīng)該同代表姓名的堆疊視圖保持一致。然而,由于堆疊視圖只"包裹"內(nèi)容,所以圖像視圖的垂直外擴(kuò)優(yōu)先級(jí)必須非常低,才能保證其主動(dòng)降低,與堆疊視圖保持一致(而非堆疊視圖增高,與圖像視圖保持一致)。此外,圖像視圖的等高約束進(jìn)一步使問(wèn)題復(fù)雜化,因?yàn)檫@導(dǎo)致水平和垂直約束互相依賴(lài)。所以文本框的水平內(nèi)縮優(yōu)先級(jí)必須非常低,否則圖像視圖不會(huì)主動(dòng)縮小。對(duì)于上述情況,將優(yōu)先級(jí)調(diào)至48或更低可以解決問(wèn)題。
Dynamic Stack View(動(dòng)態(tài)堆疊視圖)
面對(duì)堆疊視圖,如何動(dòng)態(tài)的添加和刪除內(nèi)容?接下來(lái),我們將完成這項(xiàng)挑戰(zhàn)。內(nèi)容的刪除和添加都附帶動(dòng)畫(huà)效果;另外,堆疊視圖位于滾動(dòng)視圖中,從而使得過(guò)長(zhǎng)的列表能夠滾動(dòng)。

注意
本例旨在演示如何動(dòng)態(tài)操作堆疊視圖,如何在滾動(dòng)視圖中使用堆疊視圖。實(shí)際開(kāi)發(fā)中,列表最好通過(guò)UITableView實(shí)現(xiàn)。一般來(lái)說(shuō),不建議用堆疊視圖替代列表視圖。應(yīng)根據(jù)具體需求,審慎區(qū)分不同技術(shù)的使用場(chǎng)景。
Views and Constraints(搭建布局)
界面初始布局很簡(jiǎn)單:將一個(gè)滾動(dòng)視圖置于畫(huà)布中,填充整個(gè)根視圖;向其中添加一個(gè)堆疊視圖,再向堆疊視圖中添加一個(gè)按鈕。隨后按照下圖添加約束:

1. Scroll View.Leading = Superview.LeadingMargin
2. Scroll View.Trailing = Superview.TrailingMargin
3. Scroll View.Top = Superview.TopMargin
4. Bottom Layout Guide.Top = Scroll View.Bottom + 20.0
5. Stack View.Leading = Scroll View.Leading
6. Stack View.Trailing = Scroll View.Trailing
7. Stack View.Top = Scroll View.Top
8. Stack View.Bottom = Scroll View.Bottom
9. Stack View.Width = Scroll View.Width
Attributes(設(shè)置屬性)
打開(kāi)堆疊視圖屬性面板,設(shè)置如下:
| Stack | Axis | Alignment | Distribution | Spacing |
|---|---|---|---|---|
| Stack View | Vertical | Fill | Equal Spacing | 0 |
Code(編寫(xiě)代碼)
為了添加和刪除內(nèi)容,我們需要編寫(xiě)一些代碼。創(chuàng)建一個(gè)自定義視圖控制器,使其擁有滾動(dòng)視圖和堆疊視圖。
class DynamicStackViewController: UIViewController {
@IBOutlet weak private var scrollView: UIScrollView!
@IBOutlet weak private var stackView: UIStackView!
// Method implementations will go here...
}
重寫(xiě)方法viewDidLoad,調(diào)整滾動(dòng)視圖的初始位置:我們需要滾動(dòng)視圖的內(nèi)容從狀態(tài)欄下邊開(kāi)始。
override func viewDidLoad() {
super.viewDidLoad()
// setup scrollview
let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
}
然后為按鈕編寫(xiě)動(dòng)作方法,用來(lái)添加內(nèi)容。
// MARK: Action Methods
@IBAction func addEntry(sender: AnyObject) {
let stack = stackView
let index = stack.arrangedSubviews.count - 1
let addView = stack.arrangedSubviews[index]
let scroll = scrollView
let offset = CGPoint(x: scroll.contentOffset.x,
y: scroll.contentOffset.y + addView.frame.size.height)
let newView = createEntry()
newView.hidden = true
stack.insertArrangedSubview(newView, atIndex: index)
UIView.animateWithDuration(0.25) { () -> Void in
newView.hidden = false
scroll.contentOffset = offset
}
}
上述代碼首先為滾動(dòng)視圖計(jì)算新的偏移量,然后創(chuàng)建內(nèi)容視圖。內(nèi)容視圖被添加至堆疊視圖時(shí)處于隱藏狀態(tài)。隱藏視圖對(duì)堆疊視圖布局沒(méi)有任何影響。最后,在動(dòng)畫(huà)block中顯示視圖,并更新滾動(dòng)視圖偏移量。
刪除內(nèi)容的代碼類(lèi)似;與方法addEntry不同的是,deleteStackView不通過(guò)IB與任何控件關(guān)聯(lián),而是以代碼的形式與新創(chuàng)建的內(nèi)容視圖關(guān)聯(lián)。
func deleteStackView(sender: UIButton) {
if let view = sender.superview {
UIView.animateWithDuration(0.25, animations: { () -> Void in
view.hidden = true
}, completion: { (success) -> Void in
view.removeFromSuperview()
})
}
}
這個(gè)方法通過(guò)動(dòng)畫(huà)block隱藏要?jiǎng)h除的視圖;待到動(dòng)畫(huà)結(jié)束,將其移出視圖結(jié)構(gòu),這也意味著從堆疊視圖內(nèi)容中移出。
盡管可以添加任意視圖,但這里我們使用一個(gè)堆疊視圖作為內(nèi)容視圖。它包含兩個(gè)標(biāo)簽,一個(gè)顯示日期,另一個(gè)顯示隨機(jī)十六進(jìn)制字符串,還有一個(gè)刪除按鈕。
// MARK: - Private Methods
private func createEntry() -> UIView {
let date = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .NoStyle)
let number = "\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())"
let stack = UIStackView()
stack.axis = .Horizontal
stack.alignment = .FirstBaseline
stack.distribution = .Fill
stack.spacing = 8
let dateLabel = UILabel()
dateLabel.text = date
dateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
let numberLabel = UILabel()
numberLabel.text = number
numberLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
let deleteButton = UIButton(type: .RoundedRect)
deleteButton.setTitle("Delete", forState: .Normal)
deleteButton.addTarget(self, action: "deleteStackView:", forControlEvents: .TouchUpInside)
stack.addArrangedSubview(dateLabel)
stack.addArrangedSubview(numberLabel)
stack.addArrangedSubview(deleteButton)
return stack
}
private func randomHexQuad() -> String {
return NSString(format: "%X%X%X%X",
arc4random() % 16,
arc4random() % 16,
arc4random() % 16,
arc4random() % 16
) as String
}
}
Discussion(分析&討論)
如你所見(jiàn),堆疊視圖能夠根據(jù)內(nèi)容的變化(添加或刪除),動(dòng)態(tài)調(diào)整布局。但請(qǐng)注意:
- 隱藏視圖不會(huì)對(duì)堆疊視圖布局產(chǎn)生任何影響;
- 視圖加入堆疊視圖的同時(shí),也會(huì)加入當(dāng)前視圖結(jié)構(gòu);
- 視圖從堆疊視圖中移除,不會(huì)自動(dòng)脫離當(dāng)前視圖結(jié)構(gòu);反過(guò)來(lái),視圖從當(dāng)前視圖結(jié)構(gòu)中移除,會(huì)自動(dòng)脫離堆疊視圖。
- iOS視圖屬性hidden是不可以添加動(dòng)畫(huà)效果的。但加入堆疊視圖后,就可以了,因?yàn)閯?dòng)畫(huà)效果被堆疊視圖實(shí)現(xiàn)。所以添加或刪除內(nèi)容時(shí)才有了漸隱漸現(xiàn)效果。
另外我們還使用了滾動(dòng)視圖,其內(nèi)容尺寸通過(guò)堆疊視圖定義。水平尺寸參照等寬約束;垂直尺寸等同于堆疊視圖的適配尺寸(fitting size)。添加新內(nèi)容,堆疊視圖變長(zhǎng),一旦超出屏幕高度,滾動(dòng)自動(dòng)開(kāi)啟。
更多信息,詳見(jiàn)章節(jié)Working with Scroll Views(滾動(dòng)視圖的使用)。