Visual Formate Language (VFL)

Visual Format Language

来张2016年6月的日历的日历

这个附录展示了如何使用VFL来指定自动布局的约束,包括标准间距、尺寸、垂直布局和其他不同属性的约束。此外,该附录包含VFL完整的语言语法。

VFL约束界面举例

以下是一些使用VFL约束语法的例子和示例图,注意与你所看到的图像匹配的文本。

name vfl visual
标准距离 [button]-[textField]
宽度约束 [button(>=50)]
关联父视图 |-50-[purpleBox]-50-|
纵向约束 V:[topField]-10-[bottomField]
刷新视图 [maroonView][blueView]
优先级 [button(100@20)]
宽度相等 [button1(==button2)]
多个谓词 [flexibleButton(>=70,<=100)]
一行完整的约束 |-[find]-[findNext]-[findField(>=20)]-|

NOTE
由于在markdown的表格中无法插入|,所以该文所有表格中的|均以中文中的制表符代替。

使用符号可以很好地替代可视化界面的完整性,大部分用户界面上的约束都可以使用VFL语法来表达,但有一些不能。其中的一个VFL约束无法表达的就是固定的长宽比(例如,imageView.width = 2 * imageView.height),要创建这样的约束,必须使用constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:

VFL语法

VFL的语法格式如下:

符号 替换规则
<visualFormatString> (<orientation>:)?(<superview><connection>)?<view>(<connection><view>)*(<connection><superview>)?
<orientation> H|V
<superview>
<view> [<viewName>(<predicateListWithParens>)?]
<connection> e|-<predicateList>-|-
<predicateList> <simplePredicate>|<predicateListWithParens>
<simplePredicate> <metricName>|<positiveNumber>
<predicateListWithParens> (<predicate>(,<predicate>)*)
<predicate> (<relation>)?(<objectOfPredicate>)(@<priority>)?
<relation> ==|<=|>=
<objectOfPredicate> <constant>|<viewName> (see note)
<priority> <metricName>|<number>
<constant> <metricName>|<number>
<viewName> Parsed as a C identifier. This must be a key mapping to an instance of NSView in the passed views dictionary.
<metricName> Parsed as a C identifier. This must be a key mapping to an instance of NSNumber in the passed metrics dictionary.
<number> As parsed by strtod_l, with the C locale.

NOTE
对于objectOfPredicate,viewName可以接受的只有主语的谓语是一个视图的宽度或高度。也就是说,你可以使用[view1(= = view2)]来指定view1和view2有相同的宽度。

如果你犯了语法错误,系统将会抛出一个异常信息。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
Expected ':' after 'V' to specify vertical arrangement
V|[backgroundBox]|
^
A predicate on a view's thickness must end with ')' and the view must end with ']'
|[whiteBox1][blackBox4(blackWidth][redBox]|
Unable to find view with name blackBox
|[whiteBox2][blackBox]
Unknown relation. Must be ==, >=, or <=
V:|[blackBox4(>30)]|
^

开头例子

看一下完整的图:

这是2016年6月的日历,可以根据屏幕的转换来自适应,我所想到的布局方法的话,也只有原生的VFL能够以最少的代码胜任了。

首先来写一个extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
extension UIView {
/**
自动布局一堆视图
- parameter r: 行数
- parameter l: 列数
- parameter m: 间距
- parameter sv: 父视图
- parameter creation: 创建子视图
*/
func layoutWith(rows r: Int = 1, lines l: Int = 1, margin m:CGFloat = 5, edgeInsets ei: UIEdgeInsets = UIEdgeInsetsZero, creation: (Int, Int) -> UIView) {
var HVFLs = Array<String>(count: r, repeatedValue: "H:|-\(ei.left)-")
var VVFLs = Array<String>(count: l, repeatedValue: "V:|-\(ei.top)-")
var dict: Dictionary<String, AnyObject> = [:]
for i in 0 ..< r {
for j in 0 ..< l {
let view = creation(i, j)
view.translatesAutoresizingMaskIntoConstraints = false
view.tag = 100 + i * l + j
self.addSubview(view)
func ij(ii: Int, _ jj: Int) -> String {return "VFLObj\(ii * l + jj)"}
dict[ij(i, j)] = view
HVFLs[i] += "[\(ij(i,j))\(j > 0 ? "(\(ij(i, j - 1)))" : "")]-\(j == l - 1 ? "\(ei.right)-|" : "\(m * 2)-")"
VVFLs[j] += "[\(ij(i,j))\(i > 0 ? "(\(ij(i - 1, j)))" : "")]-\(i == r - 1 ? "\(ei.bottom)-|" : "\(m * 2)-")"
}
}
for vfl in HVFLs + VVFLs {
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(vfl, options: .DirectionMask, metrics: nil, views: dict))
}
}
}

总共就20来行的代码,也看不出什么,下面我们就用一下,看看到底有什么效果:
新建一个viewcontroller,在viewDidLoad方法里加入下面几行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
self.view.layoutWith(edgeInsets: UIEdgeInsetsMake(20, 20, 20, 20)) { (_, _) -> UIView in
let view = UIView()
view.backgroundColor = UIColor.redColor().colorWithAlphaComponent(0.1)
view.layoutWith(rows: 6, lines: 7, creation: { (i, j) -> UIView in
let label = UILabel()
let curindex = i * 7 + j
label.text = "\(curindex < 3 ? curindex + 29 : (curindex - 3) % 30 + 1 )"
label.textColor = curindex > 2 && curindex < 33 ? UIColor.redColor() : UIColor.grayColor()
label.textAlignment = .Center
label.backgroundColor = UIColor.greenColor().colorWithAlphaComponent(0.1)
return label
})
return view
}

运行一下,就会发现,是不是就是上面的gif图?