之所以在用慣了snapKit之后仍談這個(gè)話題,是因?yàn)樵诤鼙热鏳emo、代碼段或stack overflow中回答別人等情況下,你需要使用autolayout并使它們生效,必須使用Apple的API進(jìn)行編碼。
我們這里不對(duì)autolayout進(jìn)行基礎(chǔ)解析,只做實(shí)際應(yīng)用。分為純API和VFL兩種方式進(jìn)行記錄。
使用API純代碼寫autolayout
Apple提供的API相對(duì)簡(jiǎn)單,不過使用上代碼較長(zhǎng),暫且不討論使用方便性。Apple提供的API有兩套:一套是iOS9之前用的使用NSLayoutConstraint,Apple可能是因?yàn)榘l(fā)現(xiàn)了使用NSLayoutConstraint代碼過長(zhǎng)的問題,在iOS9推出了NSLayoutAnchor,不僅讓約束聲明更加清晰明了,而且還通過靜態(tài)類型檢查以確保約束能夠正常工作。
1. NSLayoutConstraint
只需要?jiǎng)?chuàng)建一個(gè)NSLayoutConstraint,然后激活,添加到對(duì)應(yīng)的view即可。不過,是每一個(gè)約束都要?jiǎng)?chuàng)建,所以代碼較長(zhǎng)。創(chuàng)建一個(gè)NSLayoutConstraint只需要一個(gè)方法,為了方便,我們對(duì)每一個(gè)參數(shù)進(jìn)行注釋:
NSLayoutConstraint.init(item: Any, //要約束的目標(biāo)(比如 redView)
attribute: NSLayoutAttribute, //要約束的屬性(比如top)
relatedBy: NSLayoutRelation, //約束類型(比如equal)
toItem: Any?,//相對(duì)于哪個(gè)目標(biāo)(比如superView)
attribute: NSLayoutAttribute, //相對(duì)于這個(gè)目標(biāo)的屬性(比如bottom)
multiplier: CGFloat, //倍數(shù)(比如一半為0.5)
constant: CGFloat)//常數(shù)(差值,比如-10)
試驗(yàn)添加一個(gè)紅色的view到界面上,距上距左各20,寬200,高100.
//創(chuàng)建一個(gè)紅色的view添加到界面上
let redView = UIView()
redView.backgroundColor = .red
redView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(redView)
//添加距離頂部20
let topConstraint = NSLayoutConstraint.init(item: redView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 20)
topConstraint.isActive = true
//添加距離左邊20
let leftConstraint = NSLayoutConstraint.init(item: redView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1.0, constant: 20)
leftConstraint.isActive = true
//添加寬為200
let widthConstraint = NSLayoutConstraint.init(item: redView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 200)
widthConstraint.isActive = true
//添加高為100
let heightConstraint = NSLayoutConstraint.init(item: redView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
heightConstraint.isActive = true

Holy shit!這種最簡(jiǎn)單的約束,竟然需要寫這么多代碼,想象下你需要一個(gè)復(fù)雜界面的時(shí)候..let me die..
2. NSLayoutAnchor
iOS9之后,Apple推出了NSLayoutAnchor。NSLayoutAnchor用來創(chuàng)建NSLayoutConstraint對(duì)象,使用這些對(duì)象從而實(shí)現(xiàn)自動(dòng)布局。但是一般不會(huì)直接創(chuàng)建NSLayoutConstraint對(duì)象,而是用UIView(NSView)或者其子類,或者UILayoutGuide的某個(gè)anchor屬性(比如centerXAnchor),這些屬性對(duì)應(yīng)Auto Layout中主要的NSLayoutAttribute值(InterfaceBuilder下屬性欄可以看到),所以也可以用NSLayoutAnchor子類創(chuàng)建這些NSLayoutAttribute值.
注意:UIView本身并沒有提供anchor屬性對(duì)應(yīng)Auto Layout的margin屬性,但是UILayoutGuide有這樣的屬性與之對(duì)應(yīng)。這些屬性對(duì)應(yīng)Auto Layout中主要的NSLayoutAttribute值(InterfaceBuilder下屬性欄可以看到),所以也可以用NSLayoutAnchor子類創(chuàng)建這些NSLayoutAttribute值.
使用方法也很簡(jiǎn)單:
greenView.topAnchor.constraint(equalTo: view.topAnchor, constant: 140)
看代碼,語意非常清晰了已經(jīng)。需要注意的是,不同的約束使用的方法(參數(shù))不同,輸入greenView.topAnchor.constraint后會(huì)有代碼提示的,這里不再過多展示。
我們?cè)囋囀褂肗SLayoutAnchor繼續(xù)添加一個(gè)綠色的view到界面上,距上40,距左20,寬200,高100.
//創(chuàng)建一個(gè)綠色的view
let greenView = UIView()
greenView.backgroundColor = .green
greenView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(greenView)
//添加約束
greenView.topAnchor.constraint(equalTo: view.topAnchor, constant: 140).isActive = true
greenView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
greenView.widthAnchor.constraint(equalToConstant: 200).isActive = true
greenView.heightAnchor.constraint(equalToConstant: 100).isActive = true

OK,Fine,代碼量比去snapKit確實(shí)還是不少。需要注意的是,每一條約束,仍需要激活,可以使用
NSLayoutConstraint.activate([NSLayoutConstraint])方法,也可以直接在約束語句后設(shè)置isActive為true。
3. VFL
VFL(Visual Format Language)是Apple為了縮減NSLayoutConstant代碼推出的,以文本格式描述布局,可視化效果很好,比如:
H:|-20-[redView(50)]
水平方向:左邊框-距離20pt-長(zhǎng)50pt的redview。
這句話表示水平長(zhǎng)度為50pt的redview距離左側(cè)邊框20pt。
所謂的可視化,也就可讀性上,自己去寫,倒要費(fèi)一番功夫了。VFL的使用的方法:
NSLayoutConstraint.constraints(withVisualFormat: String, options: NSLayoutFormatOptions, metrics: [String : Any]?, views: [String : Any])
它的API短了一些,但是要湊齊參數(shù)可不是很輕便的事。參數(shù)如下:
/**
* VFL創(chuàng)建約束的API
*
* @param format 傳入某種格式構(gòu)成的字符串,用以表達(dá)想要添加的約束,如@"H:|-margin-[redView(50)]",水平方向上,redView與父控件左邊緣保持“margin”間距,redView的寬為50
* @param opts 對(duì)齊方式,是個(gè)枚舉值
* @param metrics 一般傳入以間距為KEY的字典,如: @{ @"margin":@20},KEY要與format參數(shù)里所填寫的“margin”相同
* @param views 傳入約束中提到的View,也是要傳入字典,但是KEY一定要和format參數(shù)里所填寫的View名字相同,如:上面填的是redView,所以KEY是@“redView”
*
* @return 返回約束的數(shù)組
*/
咱們?cè)儆肰FL試試,寫一個(gè)藍(lán)色的view到界面上,距上20,距左250,寬100,高200.
let blueView = UIView()
blueView.backgroundColor = .blue
blueView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blueView)
//用VFL添加約束
let hVfl = "H:|-left-[blueView(100)]"
let vVfl = "V:|-top-[blueView(200)]"
let metrics = ["top":20,"left":250]
let views = ["blueView":blueView]
let ops = NSLayoutFormatOptions.alignAllLeft
let hConsts = NSLayoutConstraint.constraints(withVisualFormat: hVfl, options: ops, metrics: metrics, views: views)
let vConsts = NSLayoutConstraint.constraints(withVisualFormat: vVfl, options: ops, metrics: metrics, views: views)
view.addConstraints(hConsts)
view.addConstraints(vConsts)

What? 說好的更簡(jiǎn)潔呢?現(xiàn)在看來,Apple為了讓你理解面向?qū)ο蠛屠斫獠季值倪^程,還是煞費(fèi)苦心,看來在能用massnory或者snapKit的情況,應(yīng)該沒人愿意使用Apple的API。
這里需要注意的是,在所有autolayout中,約束都是添加到父視圖上的,如果關(guān)聯(lián)的有多個(gè)視圖,則約束需要添加到被約束視圖的共有父視圖上的。
在ScrollView中使用autolayout
在scrollview中使用autolayout時(shí),可能稍微有些容易出錯(cuò),是因?yàn)閟crollView需要確定自己的contentSize,所以需要能確定子視圖的大小,子視圖的大小就是scrollView的contentSize。
也就是說,你需要能撐起來scrollView,且水平和豎直硬性支撐。用代表表示,就是scrollView上下左右均有約束,且子視圖的寬高一定能通過約束計(jì)算出特定的大小。
用一個(gè)實(shí)例,在scrollView中添加3個(gè)view,可左右滑動(dòng),pageEnable為true。
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.backgroundColor = .gray
scrollView.isPagingEnabled = true
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
let bgColor = [UIColor.red,UIColor.green,UIColor.yellow]
var leftView:UIView? = nil
for i in 0..<3{
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = bgColor[i]
scrollView.addSubview(view)
if let left = leftView{
view.leftAnchor.constraint(equalTo: left.rightAnchor, constant: 0).isActive = true
}else{
view.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 0).isActive = true
}
view.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true
view.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0).isActive = true
view.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0).isActive = true
leftView = view
}
//所有的view都是上左右約束到scrollView,且寬高與scrollView相同,但是scrollView右側(cè)還沒有被關(guān)聯(lián)約束
//添加右側(cè)的約束
if let left = leftView{
left.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: 0).isActive = true
}

如果是xib或者storyboard也拖出來遵循這個(gè)規(guī)則就好啦。再來重復(fù)一下需要滿足的條件:
- 約束能撐起來scrollView。就是scrollView
上下左右均有約束, - 且水平和豎直硬性支撐。所有子視圖的所需的最大寬高
一定能通過約束計(jì)算出特定的值。
autolayout做動(dòng)畫
與在frame中布局不同的是,需要在animate方法中,寫self.view.layoutIfNeeded(),僅此而已。
另外使用了autolayout的布局中,直接過去view的frame可能得到錯(cuò)誤值,在didLayoutSubviews方法中再獲取。
UIView.animate(withDuration: 0.3) {
//
self.view.layoutIfNeeded()
}