一、What's Auto Layout
Auto Layout
是由蘋果公司UIKit
框架提供的一個用於動態計算UIView
及其子類的大小和位置的庫。html
說到Auto Layout
就不得不說Cassowary
算法,由於Auto Layout
是構建在Cassowary
算法的基礎之上的。1997年,Auto Layout
用到的佈局算法論文發表,被稱爲高效的線性方程求解算法。2011年蘋果利用Cassowary
算法爲開發者提供了Auto Layout
自動佈局庫中。因爲Cassowary
算法的自己的優秀,不只是蘋果公司,許多開發者將其運用到各個不一樣的開發語言中,如JavaScript、ASP.NET、Java、C++
等都有運用Cassowary
算法的庫。從這裏也能夠看出Cassowary
算法自身的優秀和先進性,否則不會被運用的如此普遍。前端
蘋果公司在iOS 6
系統時引入了Auto Layout
,可是直到如今已經更新到iOS 12
了,還有不少開發者仍是不肯使用Auto Layout
。主要是對其反人類的語法以及對其性能問題的擔心。git
針對Auto Layout
的一些問題,在iOS 9
發佈時,蘋果推出了更簡潔語法的NSLayoutAnchor。同時發佈了模仿前端Flexbox佈局思路的UIStackView,以此爲開發者在自動佈局上提供更好的選擇。github
在蘋果WWDC 2018 High Performance Auto Layout中蘋果工程師說: iOS 12將大幅度提高Auto Layout性能,使滑動屏幕時達到滿幀。 在WWDC 2018 What's New in Cocoa Touch蘋果的工程師說了iOS 12對Auto Layout優化後的表現。 算法
iOS 11
中視圖嵌套的數量的性能快成指數級別增加了,在
iOS 12
中已經基本和手寫frame佈局的性能相似了。
從iOS 6
到iOS 12
,蘋果也在不斷的優化Auto Layout
的性能,同時爲開發者提供更簡潔的API
,若是你還在使用frame
手寫佈局,不妨試試Auto Layout
。下面我將介紹iOS
中幾種經常使用的佈局方法。bash
二、Auto Layout各個版本不一樣用法
如我要設置一個寬高爲120,居中顯示的View,效果以下圖: app
UIView *centerView = [[UIView alloc] init];
centerView.backgroundColor = [UIColor redColor];
[self.view addSubview:centerView];
CGFloat width = self.view.frame.size.width;
CGFloat height = self.view.frame.size.height;
[centerView setFrame:CGRectMake(width / 2 - (60), height / 2 - (60), 120, 120)];
複製代碼
centerView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *consW = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0
constant:120.0
];
NSLayoutConstraint *consH = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeHeight
multiplier:0
constant:120.0
];
NSLayoutConstraint *consX = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0
];
NSLayoutConstraint *consY = [NSLayoutConstraint constraintWithItem:centerView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0
];
[self.view addConstraints:@[consW,consH,consX,consY]];
複製代碼
centerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[centerView(120)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[centerView(120)]" options:0 metrics:nil views:views]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
複製代碼
__weak typeof (self) weakSelf = self;
[centerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(120, 120));
make.center.equalTo(weakSelf.view);
}];
複製代碼
let centerView:UIView = UIView.init()
view.addSubview(centerView)
centerView.backgroundColor = UIColor.red
centerView.snp.makeConstraints { (make) in
make.width.equalTo(120)
make.height.equalTo(120)
make.center.equalTo(view)
}
複製代碼
let centerView:UIView = UIView.init()
view.addSubview(centerView)
centerView.backgroundColor = UIColor.red
centerView.translatesAutoresizingMaskIntoConstraints = false
centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
centerView.widthAnchor.constraint(equalToConstant: 120).isActive = true
centerView.heightAnchor.constraint(equalToConstant: 120).isActive = true
複製代碼
經過上面的代碼對比,使用frame
手寫佈局只要幾行代碼就搞定了,使用NSLayoutConstraint
語法和VFL
語法是最複雜的,尤爲是NSLayoutConstraint
語法要用30多行代碼才能是想一樣的效果,代碼行數越多出錯的機率也就成正比上升,因此這就是不少開發者不肯使用Auto Layout
(或者說不肯意使用系統提供API來實現)的緣由之一吧。框架
若是你的App
要兼容iOS 9
如下的各個版本,建議使用Masonry,若是隻兼容iOS 9以上的版本,建議使用SnapKit或者系統提供的NSLayoutAnchor API,畢竟Masonry
這個庫已經2年沒有更新了。iview
在這裏我推薦優先使用NSLayoutAnchor
,第三方的開源庫隨時都面臨着一些問題:ide
iOS
系統版本的更新形成的適配和兼容問題,若是是開源代碼要等到蘋果發佈新版本,代碼的做者再作兼容和適配三、
Auto Layout
的生命週期
前面說到蘋果的Auto Layout
是基於Cassowary
算法的,蘋果在此基礎上提供了一套Layout Engine
引擎,由它來管理頁面的佈局,來完成建立、更新、銷燬等。
在APP
啓動後,會開啓一個常駐線程來監聽約束變化,當約束髮生變化後會出發Deffered Layout Pass
(延遲佈局傳遞),在裏面作容錯處理(若有些視圖在更新約束時沒有肯定或缺失佈局申明),完成後進入約束監聽變化的狀態。
當下一次刷新視圖(如調用layoutIfNeeded()
)時,Layout Engine
會從上到下調用layoutSubviews()
,而後經過Cassowary
算法計算各個子視圖的大小和位置,算出來後將子視圖的frame
從layout Engine
裏拷貝出來,在以後的處理就和手寫frame
的繪製、渲染的過程同樣了。使用Auto Layout
和手寫frame
多的工做就在佈局計算上。
四、
NSLayoutAnchor
經常使用屬性
對於NSLayoutAnchor
的一些經常使用屬性,經過其命名就能看出來其做用,這裏不作贅述,若是想了解更多請查閱Apple Developer NSLayoutAnchor。
五、Auto Layout幾個更新約束的方法
setNeedsLayout: 告知頁面須要更新,可是不會馬上開始更新。執行後會馬上調用layoutSubviews
。
layoutIfNeeded: 告知頁面佈局馬上更新。因此通常都會和setNeedsLayout
一塊兒使用。若是但願馬上生成新的frame
須要調用此方法,利用這點通常佈局動畫能夠在更新佈局後直接使用這個方法讓動畫生效。
layoutSubviews: 更新子View
約束
setNeedsUpdateConstraints:須要更新約束,可是不會馬上開始
updateConstraintsIfNeeded:馬上更新約束
updateConstraints:更新View
約束
六、
NSLayoutAnchor
使用注意事項
NSLayoutAnchor
爲視圖添加約束時必定要先把translatesAutoresizingMaskIntoConstraints
設置false
centerView.translatesAutoresizingMaskIntoConstraints = false
複製代碼
safeAreaLayoutGuide
適配iPhone X
等機型時要對iOS 11
以前的系統作適配,不然會致使低版本系統上程序Crashif #available(iOS 11.0, *) {
tableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
} else {
tableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
}
複製代碼
isActive
爲true
let centerX: NSLayoutConstraint = centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
centerX.isActive = true
複製代碼
leadingAnchor
不要和 leftAnchor
混用centerView.leadingAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
複製代碼
centerView.leftAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
複製代碼
以上2種寫法,在編譯時不會出現任何問題,可是在運行時就會報錯,並會致使程序Crash,官方的說法是:
While the NSLayoutAnchor class provides additional type checking, it is still possible to create
invalid constraints. For example, the compiler allows you to constrain one view’s leadingAnchor
with another view’s leftAnchor, since they are both NSLayoutXAxisAnchor instances. However,
Auto Layout does not allow constraints that mix leading and trailing attributes with left or right
attributes. As a result, this constraint crashes at runtime.
複製代碼
同理,trailingAnchor
和rightAnchor
也不能混用。
如我要修改一個UIView
的寬度: 經過代碼添加約束,可把UIView
的寬度設置類屬性,而後在須要的地方修改constant
的參數,而後在刷新約束便可,代碼以下:
var centerView: UIView!
var centerWidth: NSLayoutConstraint!
複製代碼
self.centerView = UIView.init()
view.addSubview(self.centerView)
self.centerView.backgroundColor = UIColor.red
self.centerView.translatesAutoresizingMaskIntoConstraints = false
self.centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive = true
self.centerView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
self.centerWidth = self.centerView.widthAnchor.constraint(equalToConstant: 120)
self.centerWidth.isActive = true
self.centerView.heightAnchor.constraint(equalToConstant: 120).isActive = true
複製代碼
self.centerWidth.constant = 250
weak var weakSelf = self
UIView.animate(withDuration: 0.35, animations: {
weakSelf?.centerView.superview?.layoutIfNeeded()
}) { (finished) in
}
複製代碼
效果以下:
若是是xib
或者storyboard
,那就更簡單了,直接摁住鍵盤control
鍵,拖到對應的類裏,而後在須要的地方修改約束並刷新便可。操做以下:
在開發中,咱們會遇到一些需求要求根據UIView
的寬高比來設置約束,如通常狀況下顯示視頻的寬高比是16:9,經過代碼設置寬高好比下:
centerView.heightAnchor.constraint(equalToConstant: 90).isActive = true
centerView.widthAnchor.constraint(equalTo: centerView.heightAnchor, multiplier: 16 / 9).isActive = true
複製代碼
七、
Auto Layout
自適應UITableViewCell
高度使用
rowHeight
設置高度 通常狀況下,若是UITableView
的每一個Cell
高度是固定的咱們能夠直接指定一個值便可,若是沒有設置UITableView
的高度,系統會默認設置rowHeight
高度是44。
tableview.rowHeight = 44;
複製代碼
也能夠經過UITableViewDelegate的代理來設置UItableView的高度。
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
複製代碼
若是經過手動計算每一個UItableViewCell
的高度,也在這個代理中實現,經過計算返回每一個UItableViewCell
的高度。
estimatedRowHeight
設置高度 UItableView
繼承自UIScrollView
,UIScrollView
的滾動須要設置其contentSize
後,而後根據自身的bounds、contentInset、contentOffset
等屬性來計算出可滾動的長度。而UITableView
在初始化時並不知道這些參數,只有在設置了delegate
和dataSource
以後,根據建立的UITableViewCell
的個數和加載的UITableViewCell
的高度以後才能算出可滾動的長度。
在使用Auto Layout
自適應UITableViewCell
高度時應提早設置一個估算值,固然這個估算值越接近真實值越好。
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
複製代碼
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
複製代碼
如上圖所示:這個界面就是用Auto Layout + estimatedRowHeight
完成自適應高度的,在添加約束時要按照從上到下的書訊設置每個UIView
的頂部(top
)到上一個的視圖底部的(bottom
)距離,同時要計算UITableViewCell
內部全部控件的高度。那麼問題來了,用戶發佈的內容詳情沒有獲得數據以前時沒辦法算出其高度的,此處能夠先給內容文字UILabel
設置一個默認高度,而後讓其根據內容填充自動計算高度:
topicInfoLab.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true;
topicInfoLab.font = UIFont.init(name: "Montserrat-SemiBold", size: 12)
topicInfoLab.numberOfLines = 0
複製代碼
若是用戶發佈內容沒有圖片,直接設置發佈內容UILabel距離UITableView距離底部的約束距離便可;
detailsLab.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
複製代碼
若是用戶發佈的內容有圖片,那麼在計算出每張圖片的位置和大小以後,必定要給最後一張圖片設置距離UItableViewCell
底部(bottom
)的約束距離。
for(idx, obj) in imageArray.enumerated() {
//.....計算圖片的大小和位置
if idx == imageArray.count - 1 {
//設置最後一張圖片距離底部的約束
photo.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
}
}
複製代碼
實現思路如上圖所示,具體實現的請看代碼
八、
Compression Resistance Priority
和Hugging Priority
使用
Compression Resistance Priority
和 Hugging Priority
在實際使用中每每配合使用,分別處理在同義水平線上多個view之間內容過少和內容過多而形成的互相壓擠的狀況。
Hugging Priority
的意思就是自包裹的優先級,優先級越高,則優先將尺寸按照控件的內容進行填充。
Compression Resistance Priority
,意思是說當不夠顯示內容時,根據這個優先級進行切割。優先級越低,越容易被切掉。
ContentHuggingPriority |
表示當前的UIView 的內容不想被拉伸 |
---|---|
ContentCompressionResistancePriority |
表示當前的UIView 的內容不想被收縮 |
默認狀況下: HuggingPriority = 250 |
默認狀況下: CompressionResistancePriority = 750 |
如設置2個UILabel
的拉伸優先級可以使用代碼:
fristLab.setContentHuggingPriority(UILayoutPriority(rawValue: 251), for: .horizontal)
secondLab.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
複製代碼
九、總結
本文主要分享了蘋果Auto Layout
的幾種實現方法和注意事項,對於Auto Layout
在實際開發中的使用是採用純代碼、仍是xib
+ 代碼,仍是storyboard
+ 代碼,仍是xib
+ storyboard
+ 代碼的方式實現,主要看團隊的要求、我的的習慣,以及App
的繁瑣程度。 對於Auto Layout
在視圖上的使用,我的建議若是UI比較簡單或者單一的界面可以使用Auto Layout
,若是UI的操做或刷新很複雜的界面,建議仍是frame
+ 手動佈局的方式。
友情連接:
Apple Developer High Performance Auto Layout