5. Stack Views(利用堆疊視圖布局)@Auto Layout Guide(自動(dòng)布局指南)

翻譯@Auto Layout Guide(自動(dòng)布局指南)


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)。

圖25
Views and Constraints(搭建布局)

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

圖26
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)一步添加約束。

圖27

首先構(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ù),按照下圖搭建界面,添加約束。

圖28
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)。

圖29

注意

本例旨在演示如何動(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è)按鈕。隨后按照下圖添加約束:

圖30
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)視圖的使用)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容