1. frame布局。
性能相對比較好,但當(dāng)views比較多,view依賴關(guān)系比較復(fù)雜或適配不同機型時,處理起來會比較繁瑣,代碼可讀性低。特別在數(shù)據(jù)變化或橫豎屏切換導(dǎo)致界面布局變化,通常要重新計算每個視圖的frame,工作量巨大。
2. autoresizing布局。通過設(shè)置UIView的autoresizingMask屬性來設(shè)置布局方式組合。
缺點:描述界面變化規(guī)則不夠靈活,很多變化規(guī)則根本無法精確描述。
變化規(guī)則只能基于父視圖與子視圖之間,無法建立同級視圖或者跨級視圖之間的關(guān)系。
3. Auto Layout(NSLayoutConstraint)。
Cassowary的布局算法,通過將布局問題抽象成線性不等式,并分解成多個位置間的約束, Apple 在iOS 6推出的 Auto Layout(NSLayoutConstraint),內(nèi)部使用的是該算法。
NSLayoutConstraint包含firstItem(約束視圖),secondItem(參照視圖),firstAttribute(約束視圖的屬性),secondAttribute(參照視圖的屬性),relation(關(guān)系,包括>=,=,<=), multiplier(比例系數(shù)),constant(常量),priority(優(yōu)先級)。
約束屬性中NSLayoutAttributeBaseline代表相對基線對齊。比如在UILabel中,基線是文字底部的位置,相對bottom略高。在大部分view中,基線和底部是一致的。
iOS 8對約束屬性增加了一系列帶上Margin的布局屬性,類似CSS里的padding,比如NSLayoutAttributeLeftMargin。相對于NSLayoutAttributeLeft的左對齊,NSLayoutAttributeLeftMargin一般會在左邊留出8個距離作為margin,可通過layoutMargins屬性修改。
priority優(yōu)先級只有在兩個約束有沖突的時候才起作用,優(yōu)先級高的會覆蓋優(yōu)先級低的,最高的優(yōu)先級為1000。
3.1 translatesAutoresizingMaskIntoConstraints。
UIView有個translatesAutoresizingMaskIntoConstraints屬性,對于用代碼創(chuàng)建的view,默認值是true。translatesAutoresizingMaskIntoConstraints會將 frame/autoresizing布局 自動轉(zhuǎn)化為 auto layout布局,轉(zhuǎn)化的結(jié)果是為這個視圖自動添加所有需要的約束,如果我們這時給視圖添加自己創(chuàng)建的約束就一定會約束沖突。為了避免約束沖突,需要設(shè)置translatesAutoresizingMaskIntoConstraints = false。
3.2 UILayoutGuide。
如果要實現(xiàn)布局 對多個view之間的magin動態(tài)約束(margin的值不是固定,值受到布局約束),或者實現(xiàn)多控件共同居中,一種常見的實現(xiàn)方式是使用一個或多個輔助view,專門用于實現(xiàn)它們的約束關(guān)系。但這種輔助view會增加view視圖復(fù)雜度,并會加入到事件響應(yīng)路由中。iOS 9 便推出了UILayoutGuide來代替這種輔助view,UILayoutGuide直接繼承自NSObject,并沒有真正的創(chuàng)建一個View,只是創(chuàng)建了一個矩形空間,只在進行auto layout時參與進來計算。
3.2 safeAreaLayoutGuide(繼承自UILayoutGuide)。
iOS 11 增加了safeAreaLayoutGuide 和 safeAreaInsets作為UIView的安全區(qū)屬性。safeAreaLayoutGuide用于自動布局下對子視圖建立與安全區(qū)域的約束,safeAreaInsets用于frame布局,返回view四個方向與安全區(qū)域的偏移量。safeAreaInsets在viewDidLoad獲取不到真實的值,可以在viewSafeAreaInsetsDidChange獲取。
4. NSLayoutAnchor。iOS 9 推出的自動布局類,通過設(shè)置view的不同錨來實現(xiàn)自動布局約束,內(nèi)部可以理解成也是NSLayoutConstraint實現(xiàn)。NSLayoutAnchor相對NSLayoutConstraint,代碼更加整潔,優(yōu)雅,易讀。
4. VFL。Visual Format Language 可視化格式語言是蘋果公司為了簡化Autolayout的編碼而推出的抽象語言。通過一個抽象后的字符串描述視圖的自動布局約束,簡化了代碼,增加了可讀性。
5. 自動布局SnapKit/Masonry。主流使用的自動布局框架,它們使用鏈式編程的方式對NSLayoutConstraint進行了二次封裝。舉個例子:
make.bottom.lessThanOrEqualTo(contentView.snp.bottom).multipliedBy(0.5).offset(-10). priority(.low)
可以理解成NSLayoutConstraint的如下偽代碼。
firstItem.firstAttribute.relation(secondItem. secondAttribute). multiplier. constant.priority
從snapKit源碼可以得知,SnapKit會自動將view的translatesAutoresizingMaskIntoConstraints設(shè)置為false。對于使用了snapKit的view,關(guān)閉布局向auto layout隱式轉(zhuǎn)換。
extension LayoutConstraintItem {
internal func prepare() {
if let view = self as? ConstraintView {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
5.1高級用法匯總:
5.1.1 對單個約束進行操作。
var labelConstraint: Constraint?
label.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
labelConstraint = make.right.lessThanOrEqualTo(button.snp.left).constraint
}
// 關(guān)閉約束
labelConstraint?.deactivate()
// 開啟約束
labelConstraint?.activate()
// 更新約束
labelConstraint?.update(offset: -10)
// 更改優(yōu)先級
labelConstraint?.update(priority: .low)
5.1.2 contentHuggingPriority 和 ContentCompressionResistancePriority。
UILabel、UIImageView、UIButton 在沒有設(shè)置size約束的時候,會使用數(shù)據(jù)填充計算后的intrinsicContentSize作為視圖的size約束。contentHuggingPriority(拒絕放大優(yōu)先級) 和 ContentCompressionResistancePriority(拒絕壓縮優(yōu)先級)常用于多個使用intrinsicContentSize作為自身size約束的視圖,在相互存在水平或垂直方向關(guān)聯(lián)約束,導(dǎo)致視圖需要壓縮或放大的拒絕優(yōu)先級,拒絕優(yōu)先級低的視圖優(yōu)先放大/壓縮。
在使用拒絕壓縮優(yōu)先級時,若要指定視圖滿足最小寬度,此時在極限情況,所有視圖都會出現(xiàn)壓縮,因此需要將寬度優(yōu)先級設(shè)置最高(大于所有的縮小優(yōu)先級)
let label1 = UILabel()
label1.text = "111111111111111111111111111111111111111"
view.addSubview(label1)
let label2 = UILabel()
label2.text = "222222222222222222222222222222222222222222"
view.addSubview(label2)
label1.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
label2.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
label1.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.top.equalTo(50)
// label1寬度優(yōu)先級大于label2的壓縮優(yōu)先級
make.width.greaterThanOrEqualTo(50).priority(.required)
}
label2.snp.makeConstraints { (make) in
make.right.equalToSuperview()
make.top.equalTo(50)
make.left.equalTo(label1.snp.right)
}
5.1.3 UILayoutGuide。
使用 UILayoutGuide 作為虛擬占位布局對象,可以實現(xiàn)多控件居中,動態(tài)margin等約束效果,同3.2。
使用UILayoutGuide實現(xiàn)動態(tài)margin,三等分間距效果:
func test() {
let blueView = UIView()
blueView.backgroundColor = .blue
view.addSubview(blueView)
let redView = UIView()
redView.backgroundColor = .red
view.addSubview(redView)
let leftLayoutGuide = UILayoutGuide()
let middleLayoutGuide = UILayoutGuide()
let rightLayoutGuide = UILayoutGuide()
view.addLayoutGuide(leftLayoutGuide)
view.addLayoutGuide(middleLayoutGuide)
view.addLayoutGuide(rightLayoutGuide)
blueView.snp.makeConstraints { (make) in
make.height.width.equalTo(50)
make.top.equalTo(100)
}
redView.snp.makeConstraints { (make) in
make.height.width.equalTo(50)
make.top.equalTo(100)
}
leftLayoutGuide.snp.makeConstraints { (make) in
make.left.equalToSuperview()
make.right.equalTo(blueView.snp.left)
}
middleLayoutGuide.snp.makeConstraints { (make) in
make.left.equalTo(blueView.snp.right)
make.right.equalTo(redView.snp.left)
make.width.equalTo(leftLayoutGuide)
}
rightLayoutGuide.snp.makeConstraints { (make) in
make.right.equalToSuperview()
make.left.equalTo(redView.snp.right)
make.width.equalTo(leftLayoutGuide)
}
}
使用UILayoutGuide實現(xiàn)多控件居中:
func test() {
let blueView = UIView()
blueView.backgroundColor = .blue
view.addSubview(blueView)
let redView = UIView()
redView.backgroundColor = .red
view.addSubview(redView)
let layoutGuide = UILayoutGuide()
view.addLayoutGuide(layoutGuide)
blueView.snp.makeConstraints { (make) in
make.height.equalTo(50)
make.width.equalTo(100)
make.top.equalTo(100)
}
redView.snp.makeConstraints { (make) in
make.height.width.equalTo(50)
make.top.equalTo(100)
make.left.equalTo(blueView.snp.right).offset(20)
}
layoutGuide.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.left.equalTo(blueView.snp.left)
make.right.equalTo(redView.snp.right)
}
}
5.1.4 在父視圖高度不確定,受數(shù)據(jù)填充和多個子視圖布局影響。可以通過對多個可能的底部視圖分別設(shè)定make.bottom.lessThanOrEqualTo/make.bottom.lessThanOrEqualToSuperview(),實現(xiàn)父視圖動態(tài)高度。
5.1.5 對父視圖調(diào)用layoutIfNeeded()使約束立即生效(自身調(diào)用只有size生效),可在動畫中使用產(chǎn)生約束動畫。
label1.superview?.setNeedsLayout()
UIView.animate(withDuration: 2) {
label1.snp.updateConstraints { (make) in
make.top.equalTo(200)
}
label1.superview?.layoutIfNeeded()
}
5.1.6 使用safeAreaLayoutGuide屬性,將視圖放在安全區(qū)域內(nèi)。
func test() {
let redView = UIView()
redView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
view.addSubview(redView)
redView.snp.makeConstraints { (make) in
make.edges.equalTo(self.view)
}
let blueView = UIView()
blueView.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
view.addSubview(blueView)
blueView.snp.makeConstraints { (make) in
make.edges.equalTo(self.view.safeAreaLayoutGuide)
}
}
即使不是VC的視圖,獲取的safeAreaLayoutGuide也是在安全區(qū)域中。
func test() {
let testView = TestView()
view.addSubview(testView)
testView.snp.makeConstraints { (make) in
make.left.right.equalTo(self.view.safeAreaLayoutGuide)
make.top.equalToSuperview()
make.height.equalTo(120)
}
}
class TestView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI() {
backgroundColor = .gray
let label = UILabel.init()
label.numberOfLines = 0
label.text = "123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123123"
addSubview(label)
label.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
// make.edges.equalTo(self.safeAreaLayoutGuide)
}
}
}
將 make.edges.equalToSuperview() 改成 make.edges.equalTo(self.safeAreaLayoutGuide)
// make.edges.equalToSuperview()
make.edges.equalTo(self.safeAreaLayoutGuide)
5.1.7 可通過additionalSafeAreaInsets 修改VC的安全區(qū)域范圍。
self.additionalSafeAreaInsets = UIEdgeInsets(top: 20.0, left: 50.0, bottom: 50.0, right: 50.0)
UIView的insetsLayoutMarginsFromSafeArea屬性默認為true,代表layoutMargin屬性會加上safeArea,設(shè)為false,則不會加上safeArea。
5.1.8 UIScrollView 中的 safe area。
在iOS 11以前,當(dāng)automaticallyAdjustsScrollViewInsets屬性為true,導(dǎo)航欄為半透明,VC的加入的第一個scrollView會自動調(diào)整其contentInset,以保證滑動視圖里的內(nèi)容不被UINavigationBar與UITabBar遮擋。contentInset是實際的inset。
在iOS 11或以后,取代成UIScrollView的contentInsetAdjustmentBehavior屬性,當(dāng)scrollView超出安全區(qū)域,會調(diào)整inset以防止scrollView的內(nèi)容超出安全區(qū)域。contentInset 是用戶自定義的inset,adjustedContentInset是實際的inset,并且是只讀屬性。可以理解成 contentInset + contentInsetAdjustmentBehavior調(diào)整的inset = adjustedContentInset(實際inset)。
func test() {
scrollView.backgroundColor = .purple
scrollView.contentInsetAdjustmentBehavior = .always
view.addSubview(scrollView)
scrollView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
let label = UILabel()
label.text = "123123123123"
label.textColor = .white
scrollView.addSubview(label)
label.snp.makeConstraints { (make) in
make.left.top.equalToSuperview()
}
}
UITableView 有個insetsContentViewsToSafeArea屬性,會調(diào)整自動調(diào)整顯示內(nèi)容在安全區(qū)域以內(nèi),默認為true。