1、What's Auto Layout
Auto Layout
是由蘋果公司UIKit
框架提供的一個(gè)用于動(dòng)態(tài)計(jì)算UIView
及其子類的大小和位置的庫(kù)胳蛮。
說到Auto Layout
就不得不說Cassowary
算法虐唠,因?yàn)?code>Auto Layout是構(gòu)建在Cassowary
算法的基礎(chǔ)之上的讯榕。1997年蚊惯,Auto Layout
用到的布局算法論文發(fā)表吆倦,被稱為高效的線性方程求解算法。2011年蘋果利用Cassowary
算法為開發(fā)者提供了Auto Layout
自動(dòng)布局庫(kù)中抄瓦。由于Cassowary
算法的本身的優(yōu)秀潮瓶,不僅是蘋果公司,許多開發(fā)者將其運(yùn)用到各個(gè)不同的開發(fā)語言中钙姊,如JavaScript毯辅、ASP.NET、Java煞额、C++
等都有運(yùn)用Cassowary
算法的庫(kù)思恐。從這里也可以看出Cassowary
算法自身的優(yōu)秀和先進(jìn)性沾谜,不然不會(huì)被運(yùn)用的如此廣泛。
蘋果公司在iOS 6
系統(tǒng)時(shí)引入了Auto Layout
胀莹,但是直到現(xiàn)在已經(jīng)更新到iOS 12
了基跑,還有很多開發(fā)者還是不愿使用Auto Layout
。主要是對(duì)其反人類的語法以及對(duì)其性能問題的擔(dān)憂嗜逻。
針對(duì)Auto Layout
的一些問題涩僻,在iOS 9
發(fā)布時(shí),蘋果推出了更簡(jiǎn)潔語法的NSLayoutAnchor栈顷。同時(shí)發(fā)布了模仿前端Flexbox布局思路的UIStackView,以此為開發(fā)者在自動(dòng)布局上提供更好的選擇嵌巷。
在蘋果WWDC 2018 High Performance Auto Layout中蘋果工程師說: iOS 12將大幅度提升Auto Layout性能萄凤,使滑動(dòng)屏幕時(shí)達(dá)到滿幀。
在WWDC 2018 What's New in Cocoa Touch蘋果的工程師說了iOS 12對(duì)Auto Layout優(yōu)化后的表現(xiàn)搪哪。
從圖上可以看出靡努,
iOS 11
中視圖嵌套的數(shù)量的性能快成指數(shù)級(jí)別增長(zhǎng)了,在iOS 12
中已經(jīng)基本和手寫frame布局的性能類似了晓折。
從iOS 6
到iOS 12
惑朦,蘋果也在不斷的優(yōu)化Auto Layout
的性能,同時(shí)為開發(fā)者提供更簡(jiǎn)潔的API
漓概,如果你還在使用frame
手寫布局漾月,不妨試試Auto Layout
。下面我將介紹iOS
中幾種常用的布局方法胃珍。
2梁肿、Auto Layout各個(gè)版本不同用法
如我要設(shè)置一個(gè)寬高為120,居中顯示的View,效果如下圖:
1觅彰、用frame手寫布局
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)];
2吩蔑、iOS 6提供的NSLayoutConstraint語法添加約束
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]];
3、用VFL語法
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]];
4填抬、使用第三方開源框架Masonry或SnapKit
__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)
}
5烛芬、使用iOS 9之后Apple提供的NSLayoutAnchor
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
通過上面的代碼對(duì)比,使用frame
手寫布局只要幾行代碼就搞定了飒责,使用NSLayoutConstraint
語法和VFL
語法是最復(fù)雜的赘娄,尤其是NSLayoutConstraint
語法要用30多行代碼才能是想同樣的效果,代碼行數(shù)越多出錯(cuò)的概率也就成正比上升读拆,所以這就是很多開發(fā)者不愿使用Auto Layout
(或者說不愿意使用系統(tǒng)提供API來實(shí)現(xiàn))的原因之一吧擅憔。
如果你的App
要兼容iOS 9
以下的各個(gè)版本,建議使用Masonry,如果只兼容iOS 9以上的版本檐晕,建議使用SnapKit或者系統(tǒng)提供的NSLayoutAnchor API暑诸,畢竟Masonry
這個(gè)庫(kù)已經(jīng)2年沒有更新了蚌讼。
在這里我推薦優(yōu)先使用NSLayoutAnchor
,第三方的開源庫(kù)隨時(shí)都面臨著一些問題:
-
iOS
系統(tǒng)版本的更新造成的適配和兼容問題个榕,如果是開源代碼要等到蘋果發(fā)布新版本篡石,代碼的作者再做兼容和適配 - 代碼的作者停止更新這些代碼了,這對(duì)我們開發(fā)者來說就很被動(dòng)了西采,我們要么自己修改這些代碼凰萨,要么選擇更新的開源代碼
- 使用系統(tǒng)庫(kù)可在打包時(shí)可以減少包大小
3、
Auto Layout
的生命周期
前面說到蘋果的Auto Layout
是基于Cassowary
算法的械馆,蘋果在此基礎(chǔ)上提供了一套Layout Engine
引擎胖眷,由它來管理頁面的布局,來完成創(chuàng)建霹崎、更新珊搀、銷毀等。
在APP
啟動(dòng)后尾菇,會(huì)開啟一個(gè)常駐線程來監(jiān)聽約束變化境析,當(dāng)約束發(fā)生變化后會(huì)出發(fā)Deffered Layout Pass
(延遲布局傳遞),在里面做容錯(cuò)處理(如有些視圖在更新約束時(shí)沒有確定或缺失布局申明)派诬,完成后進(jìn)入約束監(jiān)聽變化的狀態(tài)劳淆。
當(dāng)下一次刷新視圖(如調(diào)用layoutIfNeeded()
)時(shí),Layout Engine
會(huì)從上到下調(diào)用layoutSubviews()
默赂,然后通過Cassowary
算法計(jì)算各個(gè)子視圖的大小和位置沛鸵,算出來后將子視圖的frame
從layout Engine
里拷貝出來,在之后的處理就和手寫frame
的繪制放可、渲染的過程一樣了谒臼。使用Auto Layout
和手寫frame
多的工作就在布局計(jì)算上。
4耀里、
NSLayoutAnchor
常用屬性
- leadingAnchor
- trailingAnchor
- leftAnchor
- rightAnchor
- topAnchor
- bottomAnchor
- widthAnchor
- heightAnchor
- centerXAnchor
- centerYAnchor
- firstBaselineAnchor
- lastBaselineAnchor
對(duì)于NSLayoutAnchor
的一些常用屬性蜈缤,通過其命名就能看出來其作用,這里不做贅述冯挎,如果想了解更多請(qǐng)查閱Apple Developer NSLayoutAnchor底哥。
5、Auto Layout幾個(gè)更新約束的方法
setNeedsLayout: 告知頁面需要更新房官,但是不會(huì)立刻開始更新趾徽。執(zhí)行后會(huì)立刻調(diào)用
layoutSubviews
。layoutIfNeeded: 告知頁面布局立刻更新翰守。所以一般都會(huì)和
setNeedsLayout
一起使用孵奶。如果希望立刻生成新的frame
需要調(diào)用此方法,利用這點(diǎn)一般布局動(dòng)畫可以在更新布局后直接使用這個(gè)方法讓動(dòng)畫生效蜡峰。layoutSubviews: 更新子
View
約束setNeedsUpdateConstraints:需要更新約束了袁,但是不會(huì)立刻開始
updateConstraintsIfNeeded:立刻更新約束
updateConstraints:更新
View
約束
6朗恳、
NSLayoutAnchor
使用注意事項(xiàng)
- 1、在使用
NSLayoutAnchor
為視圖添加約束時(shí)一定要先把translatesAutoresizingMaskIntoConstraints
設(shè)置false
centerView.translatesAutoresizingMaskIntoConstraints = false
- 2载绿、在使用
safeAreaLayoutGuide
適配iPhone X
等機(jī)型時(shí)要對(duì)iOS 11
之前的系統(tǒng)做適配粥诫,否則會(huì)導(dǎo)致低版本系統(tǒng)上程序Crash
if #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
}
- 3、設(shè)置約束后要將其激活崭庸,即設(shè)置
isActive
為true
let centerX: NSLayoutConstraint = centerView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
centerX.isActive = true
- 4怀浆、
leadingAnchor
不要和leftAnchor
混用
centerView.leadingAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
centerView.leftAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
以上2種寫法,在編譯時(shí)不會(huì)出現(xiàn)任何問題怕享,但是在運(yùn)行時(shí)就會(huì)報(bào)錯(cuò)执赡,并會(huì)導(dǎo)致程序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
也不能混用函筋。
- 5搀玖、如何刷新某個(gè)約束
如我要修改一個(gè)UIView
的寬度:
通過代碼添加約束,可把UIView
的寬度設(shè)置類屬性驻呐,然后在需要的地方修改constant
的參數(shù),然后在刷新約束即可芳来,代碼如下:
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
UIView.animate(withDuration: 0.35, animations: { [weak self] in
guard let `self` = self else { return }
self.centerView.superview?.layoutIfNeeded()
}) { (finished) in
}
效果如下:
如果是xib
或者storyboard
含末,那就更簡(jiǎn)單了,直接摁住鍵盤control
鍵即舌,拖到對(duì)應(yīng)的類里佣盒,然后在需要的地方修改約束并刷新即可。操作如下:
- 6顽聂、設(shè)置寬高比
在開發(fā)中肥惭,我們會(huì)遇到一些需求要求根據(jù)UIView
的寬高比來設(shè)置約束,如一般情況下顯示視頻的寬高比是16:9紊搪,通過代碼設(shè)置寬高比如下:
centerView.heightAnchor.constraint(equalToConstant: 90).isActive = true
centerView.widthAnchor.constraint(equalTo: centerView.heightAnchor, multiplier: 16 / 9).isActive = true
7蜜葱、
Auto Layout
自適應(yīng)UITableViewCell
高度使用
- 使用
rowHeight
設(shè)置高度
一般情況下,如果UITableView
的每個(gè)Cell
高度是固定的我們可以直接指定一個(gè)值即可耀石,如果沒有設(shè)置UITableView
的高度牵囤,系統(tǒng)會(huì)默認(rèn)設(shè)置rowHeight
高度是44。
tableview.rowHeight = 44;
也可以通過UITableViewDelegate的代理來設(shè)置UItableView的高度滞伟。
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
如果通過手動(dòng)計(jì)算每個(gè)UItableViewCell
的高度揭鳞,也在這個(gè)代理中實(shí)現(xiàn),通過計(jì)算返回每個(gè)UItableViewCell
的高度梆奈。
- 使用
estimatedRowHeight
設(shè)置高度
UItableView
繼承自UIScrollView
,UIScrollView
的滾動(dòng)需要設(shè)置其contentSize
后野崇,然后根據(jù)自身的bounds、contentInset亩钟、contentOffset
等屬性來計(jì)算出可滾動(dòng)的長(zhǎng)度乓梨。而UITableView
在初始化時(shí)并不知道這些參數(shù)鳖轰,只有在設(shè)置了delegate
和dataSource
之后,根據(jù)創(chuàng)建的UITableViewCell
的個(gè)數(shù)和加載的UITableViewCell
的高度之后才能算出可滾動(dòng)的長(zhǎng)度。
在使用Auto Layout
自適應(yīng)UITableViewCell
高度時(shí)應(yīng)提前設(shè)置一個(gè)估算值,當(dāng)然這個(gè)估算值越接近真實(shí)值越好抵屿。
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
如上圖所示:這個(gè)界面就是用Auto Layout + estimatedRowHeight
完成自適應(yīng)高度的仪壮,在添加約束時(shí)要按照從上到下的書訊設(shè)置每一個(gè)UIView
的頂部(top
)到上一個(gè)的視圖底部的(bottom
)距離,同時(shí)要計(jì)算UITableViewCell
內(nèi)部所有控件的高度痴怨。那么問題來了,用戶發(fā)布的內(nèi)容詳情沒有得到數(shù)據(jù)之前時(shí)沒辦法算出其高度的,此處可以先給內(nèi)容文字UILabel
設(shè)置一個(gè)默認(rèn)高度忆肾,然后讓其根據(jù)內(nèi)容填充自動(dòng)計(jì)算高度:
topicInfoLab.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true;
topicInfoLab.font = UIFont.init(name: "Montserrat-SemiBold", size: 12)
topicInfoLab.numberOfLines = 0
如果用戶發(fā)布內(nèi)容沒有圖片,直接設(shè)置發(fā)布內(nèi)容UILabel距離UITableView距離底部的約束距離即可菱肖;
detailsLab.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
如果用戶發(fā)布的內(nèi)容有圖片客冈,那么在計(jì)算出每張圖片的位置和大小之后,一定要給最后一張圖片設(shè)置距離UItableViewCell
底部(bottom
)的約束距離稳强。
for(idx, obj) in imageArray.enumerated() {
//.....計(jì)算圖片的大小和位置
if idx == imageArray.count - 1 {
//設(shè)置最后一張圖片距離底部的約束
photo.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive = true
}
}
實(shí)現(xiàn)思路如上圖所示场仲,具體實(shí)現(xiàn)的請(qǐng)看代碼
8、
Compression Resistance Priority
和Hugging Priority
使用
Compression Resistance Priority
和 Hugging Priority
在實(shí)際使用中往往配合使用退疫,分別處理在同義水平線上多個(gè)view之間內(nèi)容過少和內(nèi)容過多而造成的互相壓擠的情況渠缕。
Hugging Priority
的意思就是自包裹的優(yōu)先級(jí),優(yōu)先級(jí)越高褒繁,則優(yōu)先將尺寸按照控件的內(nèi)容進(jìn)行填充亦鳞。
Compression Resistance Priority
,意思是說當(dāng)不夠顯示內(nèi)容時(shí)棒坏,根據(jù)這個(gè)優(yōu)先級(jí)進(jìn)行切割燕差。優(yōu)先級(jí)越低,越容易被切掉坝冕。
ContentHuggingPriority |
表示當(dāng)前的UIView 的內(nèi)容不想被拉伸 |
---|---|
ContentCompressionResistancePriority |
表示當(dāng)前的UIView 的內(nèi)容不想被收縮 |
默認(rèn)情況下: HuggingPriority = 250 |
默認(rèn)情況下: CompressionResistancePriority = 750 |
如設(shè)置2個(gè)UILabel
的拉伸優(yōu)先級(jí)可使用代碼:
fristLab.setContentHuggingPriority(UILayoutPriority(rawValue: 251), for: .horizontal)
secondLab.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 750), for: .horizontal)
9徒探、總結(jié)
本文主要分享了蘋果Auto Layout
的幾種實(shí)現(xiàn)方法和注意事項(xiàng),對(duì)于Auto Layout
在實(shí)際開發(fā)中的使用是采用純代碼徽诲、還是xib
+ 代碼刹帕,還是storyboard
+ 代碼,還是xib
+ storyboard
+ 代碼的方式實(shí)現(xiàn)谎替,主要看團(tuán)隊(duì)的要求偷溺、個(gè)人的習(xí)慣,以及App
的繁瑣程度钱贯。
對(duì)于Auto Layout
在視圖上的使用挫掏,個(gè)人建議如果UI比較簡(jiǎn)單或者單一的界面可使用Auto Layout
,如果UI的操作或刷新很復(fù)雜的界面秩命,建議還是frame
+ 手動(dòng)布局的方式尉共。
本文demo褒傅,請(qǐng)戳這里
本文參考:
深入剖析Auto Layout,分析iOS各版本新增特性
Auto Layout 是怎么進(jìn)行自動(dòng)布局的袄友,性能如何殿托?
Apple Developer High Performance Auto Layout
Apple Develope NSLayoutConstraint
WWDC 2018 What's New in Cocoa Touch