[Android開發學iOS系列] Auto Layout
內容:
- 介紹什么是Auto Layout.
- 基本使用方法
- 在代碼中寫約束的方法
- Auto Layout的原理
- 尺寸和優先級
- Auto Layout的使用細則
- 重要的屬性
- StackView
- Layout Guide
- Performance
- Debugging
What is Auto Layout
Auto Layout會根據constraints(約束)動態計算出view hierarchy中所有View的位置和大小.
對于Android開發者來說, Auto Layout很容易上手, 它非常像ConstraintLayout和RelativeLayout: 給View規定它上下左右和誰對齊, 決定UI的位置和大小.
Auto Layout的約束更寬泛一些, 不僅僅是兩個View之間的關系, 還有寬高, 比率等設定, 并且可以有一些大于小于等的范圍設定.
Auto Layout不是一個View
開始學Auto Layout我還以為它是一個叫AutoLayout的View, 把其他子View包進去然后設定一些放置規則, 就類似于Android的ConstraintLayout或者RelativeLayout.
但是其實不是, AutoLayout不是一個具體的View, 它代表的是一種計算引擎. 因為在代碼里你從來不需要寫AutoLayout這個關鍵字, 寫的從來都是Constraints.
開發者為View設定足夠多的約束, 規定和這個View位置和大小相關的因素, 這個引擎就可以為我們計算出View的位置和大小.
AutoLayout為了解決什么問題
不同螢屏適配; 可以合理應對變化的responsive UI.
改變布局有內外兩種因素, 除了螢屏尺寸, 螢屏旋轉, 視窗大小改變等外部因素.
內部因素還包含了內容的動態變化, 國際化的支持, 字體的調整等.
和Auto Layout平行的解決方案是什么
擺放UI有三種主要的方法:
- 在程式里給每個View設定frame.
- 設定frame, 結合使用autoresizing masks來應對外部變化. (autoresizing mask定義了一個view的frame在它的superview frame變化時應該如何變化.)
- 使用Auto Layout.
可以看出第二種只是在基于frame的方式上做出了一點改進, 所能應對的也僅僅是外部變化, 有一定的局限性. 所以可以把前兩種歸類為一種.
這也正是Auto Layout出現之前的解決方案, 即基于frame的布局方式.
Auto Layout的思考點不再著眼于view frame, 而是view的relationship.
如何使用Auto Layout
寫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, 而是選擇在代碼中創建約束, 那么仍然有多種選擇:
- 使用layout anchor.
- 使用
NSLayoutConstraint類. - 使用Visual Format Language.
我們在改變約束的時候通常不會add/remove constraints, 而是active/deactivate.
使用Layout anchor
這個方法可能是最直觀的一種方法.
// 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寫起來比較啰嗦, 必須給每個引數都指定值:
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
這個不但寫起來麻煩, 可讀性也很差.
Visual Format Language (VFL)
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的邊邊.
Auto Layout的作業原理

圖來自于: https://developer.apple.com/videos/play/wwdc2018/220
Render loop包含如上三個階段:
- update constraints從葉子節點向上.
- layout從parent節點向下執行.
- display即最后的繪制階段.
這三個階段對應的方法:

Update Constraints
它的作業是:
- 把每個公式(約束)加入計算引擎Engine里.
- 計算引擎負責解出變數: 最后的frame.
- 通知View: Superview: setNeedsLayout().
engine這里扮演一個layout cache和tracker. 收到變化時它會重新計算.
Layout
從engine得到資訊后, Subview setBounds(), subview setCenter().
尺寸和優先級
了解了Auto Layout的原理之后, 看尺寸和優先級的部分就很好理解.
Intrinsic content size
有一些View有固有內容尺寸, 對于AutoLayout來說, 會默認使用intrinsic content size, 這樣開發者就不用非得提供尺寸資訊.
默認使用: intrinsic content size. 固有內容尺寸.
- UIImageView: image size.
- UILabel: text size.
優先級
優先級的值可以從1到1000, 默認是1000.
- Required: 1000
- Default High: 750
- Default Low: 250
有優先級是因為多個constraints之間可能會有沖突, 那么約束的要求可能不能完全100%滿足, 計算引擎會在在不能滿足的情況下, 盡量地減少偏差.
約束的優先級就用來表示哪條約束我們更加關心, 更想滿足, 優先考慮.
優先級相關的變數
- content hugging priority: 尺寸比固有內容更大的可能性. 默認250. 值越小表示View更愿意擴張來滿足約束了; 值越大表示View希望盡可能地接近固有尺寸.
- content compression resistance priority: 尺寸比固有內容尺寸更小的阻力程度. 默認750. 值越大表示這個View壓縮內容的可能性越小.
Auto Layout的使用細則
Properties & Functions
有個重要的屬性要提一下:
translatesAutoresizingMaskIntoConstraints
這個屬性是為了兼容Auto Layout出現之前的基于frame布局的legacy layout系統, 幫助View在Auto Layout的世界里, 以legacy layout system的方式運作.
當這個屬性為true, 并且設定了frame時, 引擎會自動生成constraints來滿足這個frame.
這個View的屬性默認為true. 當我們要用constraints時需要設定為false.
- 當在storyboard中開始為View設定constraints時, 會自動設定為false.
- 當我們在代碼中給view設定約束之前, 需要自己顯式地把這個屬性設定為false.
如果還是用frame布局, 這個屬性不用設定成false. 比如在回圈里生成很多view的時候, 可能想有一些尺寸和位置用frame設定.
- sizeToFit(): 剛好包裹內容的大小.
Stack View
Stack View是在Auto Layout的基礎上的, 幫助我們做一些水平或者垂直的布局, 不用寫內部元素間的constraints. (類似于Android中的LinearLayout.)
往Stack View里加需要疊放的元素用的是addArrangedSubview()這個方法.
與此同時, addSubview()方法可以用來加一些別的View.
幾個屬性:
- axis: 主軸方向.
- alignment: 對齊方式.
- distribution: 沿著主軸的分布.
Stack View是比較輕量的, 所以官方會建議盡量多使用Stack View, 只在有必要的時候寫約束.
確實方便很多.
Layout Guide
很多時候為了布局的需要我們可能要包裹View或者是添加一下輔助View, 每個View都有自己的layer, 所以為了改進性能, 我們可以使用Layout Guide.
View自帶一個layoutMarginsGuide.
還挺方便的. (看了這個視頻: https://www.youtube.com/watch?v=4qPcMGiSADA)
Performance & Building Efficient Layouts
iOS12對AutoLayout的性能做了很多改進, 這個WWDC的talk有講.
關于有效率的布局, 簡而言之就是少做無用功.
Constraint Churn
constraint churning是個典型的性能問題.
churn: 攪動.
constraint churn是指更新了constraints, 但實際上view并不需要移動.
這樣是給engine發送了額外的資訊, 達到一定數量之后, 就會影響性能.
需要注意的是:
- 不要remove all constraints然后又add all. 可以把它們分組, 哪些是固定不變的, 那么addView的時候就加上, 然后activate; 對于需要動態變化的部分可以分兩組(比如一個根據內容動態決定是否需要顯示圖片的例子, 可以有兩個陣列: imageConstraints和noImageConstraints), 單獨activate/deactivate這兩組約束.
- 使用isHidden可以提高效率. 比起add/remove Subview來說.
也是WWDC2018/220里提到的, 如何避免Constraint Churn:
- Avoid removing all constraints
- Add static constraints once
- Only change the constraints that need changing
- Hide views instead of removing them
Size
可以選擇性地override一些尺寸, 減少text measure計算的程序:
- Return size if known without text measurement
- Use
UIView.noIntrinsicMetricand constraints.
System Layout Size Fitting Size
intrinsic content size是view傳給engine的.
而這個system layout size fitting size, 是從engine取出來的.
但是它有想不到的性能消耗. (every time you call the method, an engine is created and discarded.)
Debugging
Auto Layout中由約束引起的錯誤可能會有:
- 約束自相矛盾(沖突), 不能滿足, 無解. (比如一個寬度即等于100又等于200, ???)
- 約束不足導致有很多可能的解. (Engine會給出一個解, 但可能不是你想要的.)
關于怎么debug可以看: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/TypesofErrors.html#//apple_ref/doc/uid/TP40010853-CH17-SW1
大體上是根據Log還有一些可能有幫助的view的屬性和方法(供debug用).
這個視頻(https://developer.apple.com/videos/play/wwdc2015/219/)的后半段有講debug.
這里還有一個小工具網站: https://www.wtfautolayout.com/
Summary
Auto Layout是線性代數的應用實體.
有時候搬磚搬久了是不是應該慢下來欣賞一下數學的美.
References
- Understanding Auto Layout - Official Doc
- High Performance Auto Layout - WWDC2018
- Mysteries of Auto Layout, Part 1 - WWDC 2015
- Mysteries of Auto Layout, Part 2 - WWDC 2015
- Auto Layout Basics at codepath
- The Auto Layout cheat sheet
- Behind the Scenes with Auto Layout - iOS Conf SG 2019
- AutoLayout Log分析小工具
出處: 博客園: 圣騎士Wind
Github: https://github.com/mengdd
微信公眾號: 圣騎士Wind
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/526066.html
標籤:iOS
上一篇:kotlin協程小記
下一篇:kotlin協程小記
