內容:
Auto Layout會根據constraints(約束)動態計算出view hierarchy中所有View的位置和大小.
對於Android開發者來說, Auto Layout很容易上手, 它非常像ConstraintLayout
和RelativeLayout
: 給View規定它上下左右和誰對齊, 決定UI的位置和大小.
Auto Layout的約束更寬泛一些, 不僅僅是兩個View之間的關係, 還有寬高, 比率等設定, 並且可以有一些大於小於等的範圍設定.
開始學Auto Layout我還以為它是一個叫AutoLayout
的View, 把其他子View包進去然後設定一些放置規則, 就類似於Android的ConstraintLayout
或者RelativeLayout
.
但是其實不是, AutoLayout不是一個具體的View, 它代表的是一種計算引擎. 因為在程式碼裡你從來不需要寫AutoLayout
這個關鍵字, 寫的從來都是Constraints
.
開發者為View設定足夠多的約束, 規定和這個View位置和大小相關的因素, 這個引擎就可以為我們計算出View的位置和大小.
不同螢幕適配; 可以合理應對變化的responsive UI.
改變佈局有內外兩種因素, 除了螢幕尺寸, 螢幕旋轉, 視窗大小改變等外部因素.
內部因素還包含了內容的動態變化, 國際化的支援, 字型的調整等.
擺放UI有三種主要的方法:
可以看出第二種只是在基於frame的方式上做出了一點改進, 所能應對的也僅僅是外部變化, 有一定的侷限性. 所以可以把前兩種歸類為一種.
這也正是Auto Layout出現之前的解決方案, 即基於frame的佈局方式.
Auto Layout的思考點不再著眼於view frame, 而是view的relationship.
寫iOS的UI有多種方式, Auto Layout屬於UIKit, 在寫的時候, 可以用storyboard, 也可以直接在程式碼中寫約束.
在storyboard裡面有一些好處, 比如所見即所得, 而且ide會給出一些warnings, 比如控制元件在storyboard上的位置與約束不一致, 會提示, 並且可以選擇方式修復.
在storyboard裡面寫約束確實是不容易出錯的一種方式, xcode的操作也很直觀, 這裡不做演示了.
之前我們也討論過, 用storyboard寫UI存在閱讀性差, 程式碼版本管理和團隊合作都有問題等.
所以具體使用需要看實際情況.
關於約束, location和size的約束不能混著用, 這個也是從邏輯上就可以理解的.
比如讓某個view的top和parent的top對齊(或者再offset個常數)是可以的, 但是讓top等於某個size就不能理解了.
如果不用Interface Builder, 而是選擇在程式碼中建立約束, 那麼仍然有多種選擇:
NSLayoutConstraint
類.我們在改變約束的時候通常不會add/remove constraints, 而是active/deactivate.
這個方法可能是最直觀的一種方法.
// Get the superview's layout
let margins = view.layoutMarginsGuide
// Pin the leading edge of myView to the margin's leading edge
myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
// Pin the trailing edge of myView to the margin's trailing edge
myView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
// Give myView a 1:2 aspect ratio
myView.heightAnchor.constraint(equalTo: myView.widthAnchor, multiplier: 2.0).isActive = true
這裡我們把每一條約束設定了isActive = true
.
也可以直接放在一個陣列裡一起activate, 會有效能優勢:
NSLayoutConstraint.activate([
myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
myView.trailingAnchor.constraint(equalTo: margins.trailingAnchor),
myView.heightAnchor.constraint(equalTo: myView.widthAnchor, multiplier: 2.0)
])
使用NSLayoutConstraint
寫起來比較囉嗦, 必須給每個引數都指定值:
NSLayoutConstraint(item: myView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: myView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: myView, attribute: .height, relatedBy: .equal, toItem: myView, attribute:.width, multiplier: 2.0, constant:0.0).isActive = true
這個不但寫起來麻煩, 可讀性也很差.
let views = ["myView" : myView]
let formatString = "|-[myView]-|"
let constraints = NSLayoutConstraint.constraints(withVisualFormat: formatString, options: .alignAllTop, metrics: nil, views: views)
NSLayoutConstraint.activate(constraints)
用一些鍵碟符號來表達這個佈局的. (like a way of drawing the layout you want with a series of keyboard symbols)
管道符號代表parent view的邊邊.
圖來自於: https://developer.apple.com/videos/play/wwdc2018/220
Render loop包含如上三個階段:
這三個階段對應的方法:
它的工作是:
engine這裡扮演一個layout cache和tracker. 收到變化時它會重新計算.
從engine得到資訊後, Subview setBounds(), subview setCenter().
瞭解了Auto Layout的原理之後, 看尺寸和優先順序的部分就很好理解.
有一些View有固有內容尺寸, 對於AutoLayout來說, 會預設使用intrinsic content size, 這樣開發者就不用非得提供尺寸資訊.
預設使用: intrinsic content size. 固有內容尺寸.
優先順序的值可以從1到1000, 預設是1000.
有優先順序是因為多個constraints之間可能會有衝突, 那麼約束的要求可能不能完全100%滿足, 計算引擎會在在不能滿足的情況下, 儘量地減少偏差.
約束的優先順序就用來表示哪條約束我們更加關心, 更想滿足, 優先考慮.
有個重要的屬性要提一下:
translatesAutoresizingMaskIntoConstraints
這個屬性是為了相容Auto Layout出現之前的基於frame佈局的legacy layout系統, 幫助View在Auto Layout的世界裡, 以legacy layout system的方式運作.
當這個屬性為true, 並且設定了frame時, 引擎會自動生成constraints來滿足這個frame.
這個View的屬性預設為true. 當我們要用constraints時需要設定為false.
如果還是用frame佈局, 這個屬性不用設定成false. 比如在迴圈裡生成很多view的時候, 可能想有一些尺寸和位置用frame設定.
Stack View是在Auto Layout的基礎上的, 幫助我們做一些水平或者垂直的佈局, 不用寫內部元素間的constraints. (類似於Android中的LinearLayout
.)
往Stack View里加需要疊放的元素用的是addArrangedSubview()
這個方法.
與此同時, addSubview()
方法可以用來加一些別的View.
幾個屬性:
Stack View是比較輕量的, 所以官方會建議儘量多使用Stack View, 只在有必要的時候寫約束.
確實方便很多.
很多時候為了佈局的需要我們可能要包裹View或者是新增一下輔助View, 每個View都有自己的layer, 所以為了改進效能, 我們可以使用Layout Guide.
View自帶一個layoutMarginsGuide.
還挺方便的. (看了這個視訊: https://www.youtube.com/watch?v=4qPcMGiSADA)
iOS12對AutoLayout的效能做了很多改進, 這個WWDC的talk有講.
關於有效率的佈局, 簡而言之就是少做無用功.
constraint churning
是個典型的效能問題.
churn
: 攪動.
constraint churn是指更新了constraints, 但實際上view並不需要移動.
這樣是給engine傳送了額外的資訊, 達到一定數量之後, 就會影響效能.
需要注意的是:
也是WWDC2018/220裡提到的, 如何避免Constraint Churn:
可以選擇性地override一些尺寸, 減少text measure計算的過程:
UIView.noIntrinsicMetric
and constraints.intrinsic content size是view傳給engine的.
而這個system layout size fitting size, 是從engine取出來的.
但是它有想不到的效能消耗. (every time you call the method, an engine is created and discarded.)
Auto Layout中由約束引起的錯誤可能會有:
關於怎麼debug可以看: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/TypesofErrors.html
大體上是根據Log還有一些可能有幫助的view的屬性和方法(供debug用).
這個視訊(https://developer.apple.com/videos/play/wwdc2015/219/)的後半段有講debug.
這裡還有一個小工具網站: https://www.wtfautolayout.com/
Auto Layout是線性代數的應用範例.
有時候搬磚搬久了是不是應該慢下來欣賞一下數學的美.