在iOS開發中,對於控件佈局我們一般是使用AutoLayout加約束的機制實現,UIKit有一個佈局組件UIStackView,它與Flutter中的Column和Row有點類似,我們可以使用這個控件實現橫向或縱向上子視圖的佈局。
一、如何使用?
首先來了解幾個重要的屬性:
-open var axis: NSLayoutConstraint.Axis
這個屬性有horizontal
、vertical
兩種值,代表橫向佈局還是縱向佈局子控件,默認是horizontal
。設定了這個的屬性後,內部的arrangedSubviews不需要添加主軸方向上的約束(指的是vertical
時上下和horizontal
時左右)。
-open var alignment: UIStackView.Alignment
這個屬性代表內部arrangedSubviews的對齊方式, 默認.fill
public enum Alignment : Int {
// 橫向stack:貼緊頂部和底部,縱向stack則貼緊頭部尾部
case fill = 0
// 橫向stack:頂部對齊,縱向stack:頭部對齊
case leading = 1
// 橫向stack:頂部對齊(這是一個計算屬性,返回的是leading)
public static var top: UIStackView.Alignment { get }
// 橫向stack下:對齊視圖內文本的第一行基線
case firstBaseline = 2 // Valid for horizontal axis only
// 沿着軸線方向居中對齊
case center = 3
// 橫向stack:底部對齊,縱向stack:尾部對齊
case trailing = 4
// 橫stack:底部對齊(這是一個計算屬性,返回的是trailing)
public static var bottom: UIStackView.Alignment { get }
// 橫向stack下:對齊視圖內文本的最後一行基線
case lastBaseline = 5 // Valid for horizontal axis only
}
-open var distribution: UIStackView.Distribution
這個屬性代表內部arrangedSubviews的排布方式, 默認.fill。
上面面的解釋中會涉及到約束的硬知識:
- 內容擁抱優先級:視圖拒絕變爲大於其固有大小的優先級,優先級越低越容易被拉伸。
- 內容壓縮阻力優先級:視圖拒絕小於其固有大小的優先級, 優先級越低越容易被壓縮
nameView1.setContentHuggingPriority(.defaultLow, for: .horizontal) nameView1.setContentHuggingPriority(.defaultLow, for: .vertical) nameView1.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) nameView1.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
如果對
內容擁抱優先級(Content Hugging Priority)
、內容壓縮阻力優先級(Content Compression Resistance Priority)
有疑問可查看 iOS開發之AutoLayout中的Content Hugging Priority和 Content Compression Resistance Priority解析
public enum Distribution : Int {
// 適用於使UIStackView包裹內容,作用是調整內部arrangedSubviews,使它們沿着軸填充UIStackView的剩餘可用空間
// 1.當內部視圖總體超出UIStackView自身約束的高度/寬度時,
// UIStackView會根據內部視圖的內容壓縮阻力優先級來收縮,由優先級更低的來收縮;
// 2.當內部視圖總體不足以排滿UIStackView自身約束的高度/寬度時,
// UIStackView會根據內部視圖的內容擁抱優先級來擴張,由優先級更低的來擴張;
// 3. 如果優先級一致時,此時就有歧義,
// UIStackView會根據索引大小來決定,從索引最小(或者最大,不固定,按照實際開發時看到的情況決定)的view開始收縮或者擴張,直至滿足UIStackView的大小
// 4.如果UIStackView自身約束的高度/寬度是greaterThanOrEqualTo類型的,UIStackView會根據內容來伸縮。
case fill = 0
// 適用於需要使內部arrangedSubviews大小一致
// 使它們沿着軸填充UIStackView的可用空間,子視圖大小相等排布(會忽略Subviews的width和height約束)
case fillEqually = 1
// 適用於UIStackView高度/寬度約束固定了,按內部arrangedSubviews寬度高度約束比例來沿着主軸方向排布。
// 子視圖也需要設置高度/寬度約束,優先級要低於父視圖約束比如:
// make.height.equalTo(height).priority(900) // 約束優先級默認1000
case fillProportionally = 2
// 適用於內部arrangedSubviews之間需要保持相等間隔
//1. 當內部arrangedSubviews的大小不足以在主軸方向填充UIStackView時,
// 會將剩餘的空間均分給各個Subview之間間隔排布
// 2. 當內部arrangedSubviews的大小超出UIStackView時,
// 會按照內容擁抱優先級來壓縮(沒設置優先級的話,會根據索引從第一個(或者最後一個,按照實際情況)開始壓縮)
case equalSpacing = 3
// 1. 當內部arrangedSubviews不足以排滿UIStackView時,
// 會按照相等的中心距離排布,同時會保持最小的space(由space屬性決定)
// 2. 當內部arrangedSubviews超出時,會保持最小的space(由space屬性決定),並且根據內容壓縮阻力優先級來壓縮內部視圖;如果優先級未設定,則會間隔一個得壓縮subview
case equalCentering = 4
}
open var spacing: CGFloat
用來設置arrangedSubview之間的間隔,負值代表重疊;在fillProportionally
佈局下是精確的間隔,在equalSpacing
,equalCentering
佈局下代表最小的間隔。open var isBaselineRelativeArrangement: Bool
用來設定針對於垂直佈局的view之間的空隙是否以上面視圖最後一行文字的baseline,跟下面view的第一行文字的baseline來判斷。open var isLayoutMarginsRelativeArrangement: Bool
default is false! 用於設定佈局子view是否使用LayoutMargins
二、中途添加或者刪除內部的view,排列會怎麼樣變化
我們使用UIStackView來佈局內部子view時,子視圖應該以下面方法來添加或者移除. 因爲看方法很直白所以就不解釋用法了,需要注意的是:
- 執行了下面方法添加或刪除Subview,內部會重新佈局一次。
- 添加的子視圖也會添加到subviews屬性數組中。
open func addArrangedSubview(_ view: UIView)
open func removeArrangedSubview(_ view: UIView)
open func insertArrangedSubview(_ view: UIView, at stackIndex: Int)