[心得] 纯程式码 Auto Layout(二): VFL

楼主: denkeni (Denken)   2017-04-04 12:21:03
这是“纯程式码 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 参数。
作者: gogoqaz (..)   2017-04-06 22:52:00
作者: seanbabby (seanbear)   2017-05-04 17:56:00
作者: Polestar (极)   2017-05-13 00:09:00

Links booklink

Contact Us: admin [ a t ] ucptt.com