要创建一个试图,距离上下左右都是10的这样一个约束需要写上很多代码,然而现在是使用Masonry的效果
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
甚至我们这样写得更加简洁
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
接下来我们来观看下Masonry中的一些常用属性
@property (nonatomic, strong, readonly) MASConstraint *left; @property (nonatomic, strong, readonly) MASConstraint *top; @property (nonatomic, strong, readonly) MASConstraint *right; @property (nonatomic, strong, readonly) MASConstraint *bottom; @property (nonatomic, strong, readonly) MASConstraint *leading; @property (nonatomic, strong, readonly) MASConstraint *trailing; @property (nonatomic, strong, readonly) MASConstraint *width; @property (nonatomic, strong, readonly) MASConstraint *height; @property (nonatomic, strong, readonly) MASConstraint *centerX; @property (nonatomic, strong, readonly) MASConstraint *centerY; @property (nonatomic, strong, readonly) MASConstraint *baseline;
居中显示视图
UIView *myView = [[UIView alloc] init];
myView.backgroundColor = [UIColor blueColor];
[self.view addSubview:myView];
[myView mas_makeConstraints:^(MASConstraintMaker *make) { make.center.mas_equalTo(self.view); make.size.mas_equalTo(CGSizeMake(300, 300));
}];
效果图
可以看到我们已经创建出一个位置居中,并且视图大小为300×300
设置视图并排
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor redColor];
[myView addSubview:view1]; UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor yellowColor];
[myView addSubview:view2]; int padding = 10;
[view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(myView.mas_centerY); make.left.equalTo(myView).with.offset(padding); make.right.equalTo(view2.mas_left).with.offset(-padding); make.height.mas_equalTo(@120); make.width.equalTo(view2);
}];
[view2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(myView.mas_centerY);
make.left.equalTo(view1.mas_right).with.offset(padding);
make.right.equalTo(myView).with.offset(-padding);
make.height.mas_equalTo(view1);
make.width.equalTo(view1);
}];
效果图:
提醒一下,以下代码等价
make.left.equalTo(myView).with.offset(padding); 】 make.left.equalTo(myView.mas_left).with.offset(padding);
也就是说默认情况下括号里面只写了视图的时候,其自动帮你添加当前masxxx(代表前面你需要设置的约束的位置).比如上面两行代码设置的make.left,当括号里面只写了myView的时候,会自动追加为myView.mas_left。
多个视图间隔相同
注意下面设置宽度的时候是传递的数组,这样才能让多个视图进行等距离显示
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor redColor];
[myView addSubview:view1]; UIView *view2 = [[UIView alloc] init];
view2.backgroundColor = [UIColor yellowColor];
[myView addSubview:view2]; UIView *view3 = [[UIView alloc] init];
view3.backgroundColor = [UIColor greenColor];
[self.view addSubview:view3]; int padding = 10;
[view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.mas_equalTo(myView); make.left.equalTo(myView).with.offset(padding); make.right.equalTo(view2.mas_left).with.offset(-padding); make.height.mas_equalTo(@150); make.width.equalTo(@[view2, view3]);
}];
[view2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(myView);
make.height.mas_equalTo(view1);
make.width.equalTo(@[view1, view3]);
}];
[view3 mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(myView);
make.left.equalTo(view2.mas_right).with.offset(padding);
make.right.equalTo(myView).with.offset(-padding);
make.height.mas_equalTo(view1);
make.width.equalTo(@[view2, view1]);
}];
效果图:
autolayout VFL
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format
options:(NSLayoutFormatOptions)opts
metrics:(NSDictionary *)metrics
views:(NSDictionary *)views;
这个方法是我们实际编程中最常用的方法。它会根据我们指定的参数返回一组约束。 这个方法很重要,所以我会详细解释每个参数的用途。
这个参数存放的是布局逻辑,布局逻辑是使用 可视化格式语言 (VFL) 编写的。实际编程中我们也是使用VFL
编写布局逻辑,因为第一个方法明显参数过多,一个简单的布局要写很多代码。
上一个布局使用 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]];
哗,代码量减少了很多。首先我们使用 NSDictionaryOfVariableBindings(...)
宏创建了一个字典 views
,这个宏会自动把传入的对象的键路径作为字典的键,把对象作为字典的值。所以 views
字典的内容就像这样:
{@"self.view": self.view, @"view", view}
VFL
就是这两句:
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 点。
实际上我们的代码还可以简化:
...... NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2); [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]-[view2(>=50)]" 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:@"V:|-100-[view2(>=50)]" options:0 metrics:nil views:views]];
因为两个视图水平方向上是并排(从左到右)的,所以我们可以将水平方向布局的代码合并到一起。而垂直方向我们并非并排的,所以垂直方向的布局代码我们不能合并。这里所讲的并排的意思是后一个在前一个的后面,水平方向上明显是这样,但垂直方向上两个视图的 y
是相同的,所以无法合并在一起布局。
例二:我们继续添加一个视图 view3
填补 view
右下方的空缺,效果如图:
代码如下:
UIView *view = [UIView new]; [view setBackgroundColor:[UIColor redColor]]; [self.view addSubview:view]; UIView *view2 = [UIView new]; [view2 setBackgroundColor:[UIColor blueColor]]; [self.view addSubview:view2]; UIView *view3 = [UIView new]; [view3 setBackgroundColor:[UIColor orangeColor]]; [self.view addSubview:view3]; [view setTranslatesAutoresizingMaskIntoConstraints:NO]; [view2 setTranslatesAutoresizingMaskIntoConstraints:NO]; [view3 setTranslatesAutoresizingMaskIntoConstraints:NO]; NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2, view3); [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(50)-[view(>=150)]-[view2(>=50)]" 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]-[view3(>=50)]" options:0 metrics:nil views:views]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(100)-[view2(>=50)][view3(>=100)]" options:0 metrics:nil views:views]];
你可能注意到我把每个间距都使用小括号阔了起来,这是可选的,你完全可以直接写间距,这么写只是告诉你还有这种语法。实际上没什么必要这么写,因为 VFL
语法并不支持运算,例如把 (50)
切分为(10+40)
或 (5*10)
都是不合法的。
最后两行是 view3
的布局代码,简单解释一下:
H:[view]-[view3(>=50)]
水平方向布局,view3
在 view
右侧标准距离处,并且宽度不小于 50 点。
V:|-(100)-[view2(>=50)][view3(>=100)]
垂直方向布局,view2
距离父视图(顶部)100 点,并且高度不小于 50 点;view3
紧挨着view2
底部(没有 -
),并且高度不小于 100 点。
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
使用此值。
因为是位掩码,所以我们可以使用 |
进行多选,例如例一,我们希望在现有约束的基础上让两个视图的高度相等,那代码可以这样写:
...... NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2); [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]-[view2(>=50)]" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];
指定两个视图的顶部和底部约束相同,然后只设置其中一个视图的相关约束即可。
灵活使用此参数可以节省不少时间,但这个参数内容太多,如果你有兴趣了解,可以看看我的另一篇博文:《Auto Layout 中的排列选项》
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(...)
宏来快速创建,代码如下:
...... [view setTranslatesAutoresizingMaskIntoConstraints:NO]; NSNumber *left = @50.f; NSNumber *top = @100.f; NSNumber *width = @150.f; NSNumber *height = @150.f; NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view); NSDictionary *metrics = NSDictionaryOfVariableBindings(left, top, width, height); [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]];
使用第三方框架 Masonry 实现自动布局
__weak typeof(self) weakSelf = self;
UIView * tempView = [[UIView alloc]init];
NSInteger count = 10;//设置一排view的个数
NSInteger margin = 10;//设置相隔距离
NSInteger height = 50;//设置view的高度
for (int i = 0; i < count; i ++) {
UIView * view = [[UIView alloc]init];
view.backgroundColor = [UIColor brownColor];
[self.view addSubview:view];
if (i == 0) {
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(weakSelf.view).offset(margin);
make.centerY.equalTo(weakSelf.view);
make.height.mas_equalTo(height);
}];
}
else if (i == count – 1){
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(weakSelf.view).offset(-margin);
make.left.equalTo(tempView.mas_right).offset(margin);
make.centerY.equalTo(tempView);
make.height.equalTo(tempView);
make.width.equalTo(tempView);
}];
}
else{
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(tempView.mas_right).offset(margin);
make.centerY.equalTo(tempView);
make.height.equalTo(tempView);
make.width.equalTo(tempView);
}];
}
tempView = view;
[view layoutIfNeeded];
}