AutoLayout 使用詳解

WHC發表於2016-12-24

前言


故事從一年前說起,當時由於接到一個新專案開發任務開發之前想了想以前專案UI佈局方式大多數都是frame計算有的也用到masonry
frame大家都知道適配各種螢幕非常繁瑣各種座標size計算程式碼很冗餘後期難以維護。
masonry開源給iOS開發者帶來福音簡化了AutoLayout使用方式,但是我覺得masonry還不足夠快捷方便(有的api不知道什麼意思學習成本比較高),尤其是動態佈局masonry更新約束相當不方便,後來就決定自己開發AutoLayout庫也就是今天WHC_AutoLayoutKit

在閱讀之前可以先看看例子專案:github.com/netyouli/WH…

簡介


  • API採用鏈式呼叫(快捷方便)一行程式碼搞定佈局
  • 提供【Objective-C】【Swift2.3】【Swift3.0】三種語言版本庫
  • 包含一行程式碼計算UITableViewCell高度模組帶快取高度
  • 包含WHC_StackView模組(目的替代系統UIStackView)
  • 隱式更新約束技術(核心後面重點介紹)

鏈式呼叫


view.whc_Left(10)      //view與父檢視左邊距10
    .whc_Right(10)     //view與父檢視右邊距10
    .whc_Height(40)    //view自身高度40
    .whc_Top(64)       //view與父檢視頂邊距64複製程式碼

一行程式碼計算Cell高度


func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewCell.whc_CellHeightForIndexPath(indexPath, tableView: tableView)
}複製程式碼

隱式更新約束


什麼叫隱式更新,顧名思義就是在你新增同型別約束(可能會產生衝突約束)會自動刪除前面新增可能產生衝突的約束(更新約束)看下面例子:

override func viewDidLoad() {
    super.viewDidLoad()
    let view = UIView()
    self.view.addSubview(view)

    view.whc_Left(10)       //view與父檢視左邊距10
        .whc_Right(10)      //view與父檢視右邊距10
        .whc_HeightAuto()   //view高度自動
        .whc_Top(64)        //view與父檢視頂邊距64
}複製程式碼

有時候程式執行的過程中根據需求需要動態調整UI佈局假如點選按鈕上面view高度調整固定64程式碼如下:

// 單獨更新height約束
private func clickButton(sender: UIButton) {
    view.whc_Height(64)   // 只需要執行這一行程式碼即可更新view高度為64
}複製程式碼

上面這個程式碼執行做了什麼事情呢?
他會檢查view高度方向是否有同型別可能衝突約束如果檢查到那麼會刪除上面新增的HeightAuto約束然後新增新Height固定約束64。
預設情況固定高度約束優先順序比自動高度約束高所以即使不刪除上面HeightAuto也沒關係,但是有時候會因為約束衝突程式崩潰。再比如點選按鈕如下修改:

// 單獨更新top約束
private func clickButton(sender: UIButton) {
    view.whc_Top(10, toView: otherView)   //viwe 頂部間隙到otherView底部為10
}複製程式碼

同樣上面的程式碼執行之前會檢查y方向是否有同型別約束(可能衝突的約束),顯然view.whc_Top(10)view.whc_Top(10, toView: otherView)肯定是衝突的,所以在執行上面程式碼WHC_AutoLayout會先刪除viewwhc_Top(10)約束然後再新增whc_Top(10, toView: otherView)約束。
上面解釋就是隱式更新約束技術而不需要像masonry重新重寫view所有約束那麼麻煩。

UIView高度自動


override func viewDidLoad() {
    super.viewDidLoad()
    let view = UIView()
    self.view.addSubview(view)

    let label = UILabel()
    self.view.addSubview(label)
    label.text = "xxxxxxxxxxxxxxxxxxxxx"
    label.whc_Left(10)      //label左到view左邊距10
         .whc_Right(10)     //label右到view右邊距10
         .whc_Top(10)       //label頂到view頂邊距10
         .whc_HeightAuto()  //label高度自動
         .whc_Bottom(10, keepHeightConstraint: true) //label底到view底邊距10,並且保留label高度

    view.whc_Left(10)       //view與父檢視左邊距10
        .whc_Right(10)      //view與父檢視右邊距10
        .whc_Top(64)        //view與父檢視頂邊距64
        .whc_HeightAuto()   //view高度自動
}複製程式碼

效果如下:

AutoLayout 使用詳解

下面對上面程式碼做解釋:
為什麼label需要5個約束?那是因為view高度需要自動根據label高度自動調整,而label高度本身是自動的如果不新增labelview的底邊距whc_Bottom關係約束view無法根據label高度變化而變化。那可能又有人疑問?whc_Bottom(10, keepHeightConstraint: true)裡的keepHeightConstraint是什麼意思?前面介紹了WHC_AutoLayout是隱式更新約束技術然而很顯然labelwhc_HeightAuto一般情況和whc_Bottom是同型別約束(衝突約束)所以這兩個一般情況只能存在一個約束,但是iOS有一種特殊情況需要Height約束和Bottom約束同時存在那就是在view自動高度的時候(bottom為了撐開父檢視因為父檢視是自動高度所以需要一個自動高度參照約束)或者view底邊距對齊(不採用top對齊)的時候如:

label.whc_Left(10)      //label左到view左邊距10
     .whc_Right(10)     //label右到view右邊距10
     .whc_HeightAuto()  //label高度自動
     .whc_Bottom(10, keepHeightConstraint: true) //label底部間隙和父檢視底部10複製程式碼

上面label就是一種從下往上佈局。

總結


從上面的例子與介紹可以我們可以對WHC_AutoLayout得出如下結論:

  • 一個控制元件在不使用帶keep的約束API時候不管後面新增多少約束永遠只會存在4個
  • 一個控制元件同方向約束不管後面新增多少約束永遠只會保留最後新增的同型別約束自動刪除前面其他同型別約束
  • 一個view需要高度或者寬度自動適應時候其view上最後一個控制元件需要用到5個約束(這個時候需要用到帶keep的API,同樣從下或者從右開始佈局有時候也需要)
  • 關於WHC_StackView後面會有專門的文章詳細介紹

    附件


1.x方向同型別約束(不會對寬度產生影響):
Left,Leading,Trailing,CenterX(包含ToView...)
注意WHC_AutoLayoutLeadingTrailing特殊處理理論上他們是可以成對使用的為了統一性把他們歸為同類約束Leading左對齊Trailing又對齊
2.y方向同型別約束(不會對寬度產生影響):
Top,BaseLineSpace,CenterY(包含ToView...)
3.寬度方向同型別約束(對寬度產生影響):
Width,Right(包含ToView,自動寬度...)
4.高度方向同型別約束(對高度產生影響):
Height,Bottom(包含ToView,自動高度...)


WHC_AutoLayout開源地址github.com/netyouli/WH…
本人其他優秀開源專案:github.com/netyouli/

致敬

謝謝你的耐心閱讀

掘金徵文: gold.xitu.io/post/58522d…