相对于其他的自动布局语言来说VFL代码量减少了很多,就比如要实现下面这个约束:
布局代码:
UIView *view = [UIView new];
[view setBackgroundColor:[UIColor redColor]];
[self.view addSubview:view];
//传统的布局方式
CGRect viewFrame = CGRectMake(50.f, 100.f, 150.f, 150.f);
// 使用 Auto Layout 布局
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
// `view` 的左边距离 `self.view` 的左边 50 点.
NSLayoutConstraint *viewLeft = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1
constant:CGRectGetMinX(viewFrame)];
// `view` 的顶部距离 `self.view` 的顶部 100 点.
NSLayoutConstraint *viewTop = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:CGRectGetMinY(viewFrame)];
// `view` 的宽度 是 60 点.
NSLayoutConstraint *viewWidth = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:CGRectGetWidth(viewFrame)];
// `view` 的高度是 60 点.
NSLayoutConstraint *viewHeight = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:CGRectGetHeight(viewFrame)];
// 把约束添加到父视图上.
[self.view addConstraints:@[viewLeft, viewTop, viewWidth, viewHeight]];
如果使用VFL语言只需要下面这几句
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];
H:|-50-[view(>=150)]
V:|-100-[view(>=150)]
第一句是在水平方向布局,表示 view 左边距离父视图左边 50 点,宽度至少 150 点。(水平方向是宽度)
第二句是在垂直方向上布局,表示 view 顶部距离父视图顶部 100 点,宽度至少 150 点。(垂直方向是高度)
分解说明如下:
H / V 表示布局方向。H 表示水平方向(Horizontal),V 表示垂直方向(Vertical),方向后要紧跟一个 :,不能有空格。
| 表示父视图。通常出现在语句的首尾。
- 有两个用途,单独一个表示标准距离。这个值通常是 8 ;两个中间夹着数值,表示使用中间的数值代替标准距离,如第一句的 -50-,就是使用 50 来代替标准距离。
[] 表示对象,括号中间需要填上对象名,对象名必须是我们传入的 views 字典中的键。对象名后可以跟小括号 (),小括号中是对此对象的尺寸和优先级约束。水平布局中尺寸是宽度,垂直布局中尺寸是高度。如第一句中的 (>=150) 就是对 view 尺寸的约束,因为是水平方向布局,所以它表示宽度大于或等于 150 点。而 150 前面的 >= 就是我们上面第一个方法中提到的关系参数。括号中可以包含多条约束,如果我们想再加一条约束,保证 view 的宽度最大不超过 200 点,我们可以这样写:H:|-50-[view(>=150,<=200)]。
VFL 语法有几点需要注意:
布局语句中不能包含空格
和关系一样,没有 >、< 这种约束
然后下面是一些例子,增加你对 VFL 语法的理解。
例一:
我们在 view 右侧添加另一个视图 view2,效果如图:
实现代码:
UIView *view = [UIView new];
[view setBackgroundColor:[UIColor redColor]];
[self.view addSubview:view];
UIView *view2 = [UIView new];
[view2 setBackgroundColor:[UIColor blueColor]];
[self.view addSubview:view2];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[view2 setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[view]-[view2(>=50)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view2(>=50)]" options:0 metrics:nil views:views]];
我们讲讲最后的两条新的 VFL 语句:
H:[view]-[view2(>=50)]
从开始的 H: 我们可以判断出这是水平方向的布局,换句话说就是设置视图的 x 和 width。接着的 [view],说明后面的所有视图都是在 view 的右侧;接着是 -,说明后一个视图和 view 之间有一个标准距离的间距;也就是说 x 等于 view 的右侧再加上标准距离,即 CGRectGetMaxX(view) + 标准距离。最后是 [view2(>=50)],这里可以看出后一个视图是 view2,并且它的宽度不小于 50 点。整一句翻译成白话就是说:在水平方向上,view2 在 view 右侧的标准距离位置处,并且它的宽度不小于 50 点。
V:|-100-[view2(>=50)]
从开始的 V: 我们可以判断出这是垂直方向的布局,换句话说就是设置视图的 y 和 height。接着的 | 说明是后一个视图是相对于父视图进行布局;接着是 -100-,说明垂直方向和父视图(顶部)相距 100 点,也就是说 y 等于 100 点。最后是 [view2(>=50)],这和上一句相同,只是因为是垂直方向,所以 50 是设置高度而不是宽度。整一句翻译成白话就是说:在垂直方向上,view2 在相对于父视图(顶部) 100 点的位置处,并且它的高度不小于 50 点。
下面我们说一下各个参数的作用和意义
options
这个参数的值是位掩码,使用频率并不高,但非常有用。它可以操作在 VFL 语句中的所有对象的某一个属性或方向。例如上面的例一,水平方向有两个视图,它们的垂直方向到顶部的距离相同,或者说顶部对齐,我们就可以给这个参数传入 NSLayoutFormatAlignAllTop 让它们顶部对齐,这样以来只需要指定两个视图的其中一个的垂直方向到顶部的距离就可以了。代码:
...
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]-[view2(>=50)]" options:NSLayoutFormatAlignAllTop metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[view2(>=50)]" options:0 metrics:nil views:views]];
它的默认值是 NSLayoutFormatDirectionLeadingToTrailing,根据当前用户的语言环境进行设置,比如英文中就是从左到右,希伯来语中就是从右到左。
这个值符合我们常用的选项。NSLayoutFormatDirectionLeadingToTrailing 的值是 0 << 16,所以我们可以直接传入 0 使用此值。
因为是位掩码,所以我们可以使用 | 进行多选
metrics
这是一个字典,字典的键必须是出现在 VFL 语句中的字符串,值必须是 NSNumber 类型,作用是将在 VFL 语句中出现的键替换为相应的值。例如本文中的第一个布局的例子,使用了这个参数后代码就变成了这样:
UIView *view = [UIView new];
[view setBackgroundColor:[UIColor redColor]];
[self.view addSubview:view];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
CGRect viewFrame = CGRectMake(50.f, 100.f, 150.f, 150.f);
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view);
NSDictionary *metrics = @{@"left": @(CGRectGetMinX(viewFrame)),
@"top": @(CGRectGetMinY(viewFrame)),
@"width": @(CGRectGetWidth(viewFrame)),
@"height": @(CGRectGetHeight(viewFrame))};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[view(>=width)]" options:0 metrics:metrics views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view(>=height)]" options:0 metrics:metrics views:views]];
聪明的你看了这段代码后肯定已经明白这个参数的用途了,虽然使用频率不高,但依然很有用,特别是要动态计算约束值的时候非常有用。
实际上这个参数也可以使用 NSDictionaryOfVariableBindings(...) 宏来快速创建
又是一个字典,包含了 VFL 语句中用到的视图。字典的键必须是出现在 VFL 语句中的视图名称,值必须视图的实例。这个字典我们在讲 format 时已经讲过,也用过很多次,相信你早已明白是怎么回事了。
讲了这么多,可能你也发现了,只要学会了 VFL 语法,就可以方便地使用 Auto Layout 了,其他的知识都属于辅助选项,会的话,布局更轻松一些,不会也没关系,实践多了,自然就会了。
优先级 (Priority level)
约束条件有优先级,高优先级约束会比低优先级约束优先得到满足,系统内置了 4 个优先级:
enum {
UILayoutPriorityRequired = 1000,
UILayoutPriorityDefaultHigh = 750,
UILayoutPriorityDefaultLow = 250,
UILayoutPriorityFittingSizeLevel = 50,
};
typedef float UILayoutPriority;
UILayoutPriorityRequired 这是默认值,这意味着这个约束条件必须被精确地满足。
UILayoutPriorityDefaultHigh
UILayoutPriorityDefaultLow
UILayoutPriorityFittingSizeLevel 这是内置的最低优先级。
相信你已经看到每个等级的数值了,优先级的取值在 0 ~ 1000 之间,取值越大,优先级越高,越会被优先满足。
每个约束的默认优先级就是 UILayoutPriorityRequired,这意味着你给出的所有约束都必须得到满足,一旦约束间发生冲突,你的应用就会 Crash。这也是在使用 Auto Layout 时经常会犯的错误:没有给约束设置适当的优先级。
最后 补充一点复杂一些的用法
//relatedBy 是指大于等于 等于 小于等于中的一种