Swift中用API純代碼寫autolayout

之所以在用慣了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
NSLayoutConstraint

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

NSLayoutAnchor

OK,Fine,代碼量比去snapKit確實(shí)還是不少。需要注意的是,每一條約束,仍需要激活,可以使用NSLayoutConstraint.activate([NSLayoutConstraint])方法,也可以直接在約束語句后設(shè)置isActivetrue。

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)
VFL

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
}
scrollView

如果是xib或者storyboard也拖出來遵循這個(gè)規(guī)則就好啦。再來重復(fù)一下需要滿足的條件:

  1. 約束能撐起來scrollView。就是scrollView上下左右均有約束,
  2. 且水平和豎直硬性支撐。所有子視圖的所需的最大寬高一定能通過約束計(jì)算出特定的值。
autolayout做動(dòng)畫

與在frame中布局不同的是,需要在animate方法中,寫self.view.layoutIfNeeded(),僅此而已。
另外使用了autolayout的布局中,直接過去view的frame可能得到錯(cuò)誤值,在didLayoutSubviews方法中再獲取。

UIView.animate(withDuration: 0.3) {
    //
    self.view.layoutIfNeeded()
}

文中代碼下載: [https://github.com/xueyongwei/AutpLayoutAPI]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容