這是「純程式碼 Auto Layout 與概要教學」系列第二篇。總共會有三篇:
* 從 setFrame 到 Auto Layout constraint
* Visual Format Language (VFL)
* 何時需要 UIStackView?
本系列文範例,都整理成 Swift Playground 在這專案裡
https://github.com/denkeni/Auto-Layout-Programmatically
(由於批踢踢不方便貼程式碼,會精簡摘錄
含完整程式碼與後續文章更新,請到網頁版 https://goo.gl/AWOCI2 )
# 前言
純程式碼建立 Auto Layout constraint
在 iOS 6 剛推出時只有兩種方式
其一是前篇文所述的線性關係
其二即是 Visual Format Language (VFL)
到 iOS 9 之後才有第三種方式 NSLayoutAnchor
然而蘋果推出 Auto Layout 至今
幾乎所有官方文件都推廣透過 Interface Builder 來設定 constraint
這使得長期以來官方文件僅短短一頁的 VFL 成為被束之高閣的瑰寶
# Visual Format Language (VFL)
VFL 可讀性不錯,尤其在相當複雜程度的排版更是如此,
因為 VFL 的根本基礎,就只有一段即見即所得的字串:
```
NSLayoutConstraint.constraints(withVisualFormat: VFLString,
options: NSLayoutFormatOptions,
metrics: [String : Any],
views: [String : Any])
```
直接用實例來解釋這個 API
我們改用 Auto Layout VFL 重做前篇例子 subviewB 的排版方式
分別設定 top, bottom, left, right 如下:
https://cdn-images-1.medium.com/max/800/1*PZarxIPo6anPxjm8VysOhw.png
```精簡版
VisualFormat: "V:|-(150)-[subviewB]-(150)-|"
// V: Vertical
VisualFormat: "H:|-(100)-[subviewB]-(100)-|"
// H: Horizontal
```
首先可以發現,VFL 其實就是自動幫我們生成多個 constraints
所以改用 += 來加入 constraints array
以中括號描述 view
以小括號描述數值
座標軸同樣是由上往下(V)、由左向右(H)的方向描述
直線 `|` 表示為 superview
`V:|-(150)-[subviewB] `表示為 subviewB 與其 superview 的 top 相距 150pt
`V:[subviewB]-(150)-|` 表示為 subviewB 與其 superview 的 bottom 相距 150pt
兩者合併起來就是相當直觀的 `V:|-(150)-[subviewB]-(150)-|`
多數人不使用 options 而採用預設的空值 []
metrics 是用來描述 VFL 字串中「數值常數」的對應字典
views 則是用來描述 VFL 字串中「view 變數」的對應字典
故前例也可以修改成這樣:
```精簡版
let metrics = ["v": 150, "h": 100]
VisualFormat: "V:|-(v)-[subview]-(v)-|"
VisualFormat: "H:|-(h)-[subview]-(h)-|"
```
由此也會發現
只用 VFL 無法描述前篇例子 subviewA, subviewC 的排版方式
因為 VFL 無法描述 centerX, centerY
因此,在許多情況下
VFL 還是得合併使用 constraint 來正確描述 subview
我們改用 Auto Layout VFL + NSLayoutAnchor 重做前篇例子 subviewA, subviewC 的排版方式
分別設定 centerX, centerY 和 width, height 如下:
```精簡版
subviewA.centerXAnchor.constraint(equalTo: view.centerXAnchor)
subviewA.centerYAnchor.constraint(equalTo: view.centerYAnchor)
VisualFormat: "H:[subviewA(200)]"
// width
VisualFormat: "V:[subviewA(100)]"
// height
```
基於 VFL,也可以很容易地描述較為複雜的排版了:
https://cdn-images-1.medium.com/max/800/1*_7pEUtg78EELsPmSXpL2UQ.png
```精簡版
let metrics = ["p": 15] // padding
VisualFormat:
"H:|-(p)-[subview1(100)]-(10)-[subview2(120)]-(10)-[subview3]-(p)-|"
"V:|-(p)-[subview1]-(p)-|"
"V:|-(p)-[subview2]-(p)-|"
"V:|-(p)-[subview3]-(p)-|"
```
當然,在描述更為複雜的二維排版時
由於一段 VFL 只能描述一維排版關係
經常需要組合多段 VFL 才能完整而正確地描述排版
# 番外篇:NSLayoutFormatOptions
這個選項可在描述 X 方向 VFL 時,提供 Y 方向排版的準則
反之亦然。
以下述為例:
https://cdn-images-1.medium.com/max/800/1*pyc3FixPF8775DPTkQTnVQ.png
```精簡版
VisualFormat:
"H:|-(15)-[subview1(100)]-(10)-[subview2(120)]-(10)-[subview3]-(15)-|",
options: .alignAllTop, metrics: nil, views: viewsDict)
VisualFormat: "V:[subview1(300)]"
VisualFormat: "V:[subview2(200)]"
VisualFormat: "V:[subview3(150)]"
```
# VFL Coding Style
以下是我自己偏好的 VFL Coding Style 原則,並藉此解釋 VFL 的相容語法:
* view 間距常數值加上括號
* view 間距常數值為等式時,不加上 `==`
ex. `[view1]-(15)-[view2]`
而非 `[view1]-15-[view2]` 或 `[view1]-(==15)-[view2]`
這也是考量了使用不等式時,必須加上括號才能運作
ex. `[view1]-(>=15)-[view2]`
* view 間距常數值為 0 時,依然要標示出來
ex. `[view1]-(0)-[view2]` 而非 `[view1][view2]`
ex. `|-(0)-[view]-(0)-|` 而非 `|[view]|`
* 不使用預設的間距
ex. `[view1]-(8)-[view2]` 而非 `[view1]-[view2]`(兩者效果相同)
P.S. 有一專案 [Swiftstraints](https://github.com/Skyvive/Swiftstraints)
將 VFL 精簡成僅需一段字串,字串內可直接取用變數,
便可省略了 metrics 與 views 參數。