【iOS】Autoresizing、AutoLayout、VFL

布局千变万化,但万变不离其宗。

历史背景

iPhone4s及以前的设备尺寸都是固定320*480(为什么这么设计呢?乔帮主认为这是人类手持设备最适合的尺寸),但随着时间推移及技术发展,不同设备尺寸层出不穷。

iPhone5出来后开发者就要做界面适配了,因为设备尺寸变成320*568了。Autoresizing其实主要是为了解决iPad的界面适配(横竖屏),iPhone也是适用的,但Autoresizing局限性较大,它只能设置自身和父控件的关系,对于一些复杂的界面根本无法完成 。iPhone5(iOS6)已经发布了AutoLayout技术,主要是为了解决移动端屏幕适配问题,比Autoresizing更丰富,但当时开发工具版本是Xcode4,对Autolayout的支持很不友好,再加上需要往下兼容iOS5甚至iOS4的系统,所以很少有开发者使用它。

iPhone6iPhone6Plus发布后,开发工具也随之升级到Xcode5Autolayout的开发效率也有很大提升,苹果官方也推荐使用Autolayout来布局UI界面。因为它可以很轻松解决屏幕适配问题(解决任何控件之间的相对关系),开发者才开始逐步使用Autolayout

iOS8之后又推出了Sizeclasses,可以理解为AutoLayout的升级版。但我觉得后面苹果应该会往声明式编程的大前端行进,Flex布局也会一统天下,到那时界面布局应该会稳定下来吧……

Autoresizing

Autoresizing其实很简单,因为它只能约束子控件和父控件之间的关系,不能约束兄弟控件的关系,这也是它最大的壁垒。我们先通过Storyboard演示下Autoresizing是如何使用的,然后再看纯代码的写法。

Storyboard

由于AutoresizingAutoLayout是互斥的,所以如果你的开发工具是Xcode8以下的,需要选择Main.storyboard后点击右边文件简介区顶部的第一个按钮(Show the File inspector),将Use Auto LayoutUser Size Classes两个复选框取消勾选(默认是勾选的)。当取消勾选Use Auto Layout时系统会弹出标题为Using Size Classes Requires Auto Layout的弹框,选择Disable Size Classes即可(此时User Size Classes会自动取消勾选)。Xcode8及以上不需要做任何改动。

案例一(场景:红色view位于屏幕右下角且宽高均为100,根据屏幕自适应位置及尺寸)

添加view
添加view

添加完成后预览,在8Plus上展示没有问题,但我们在4s(只是作为不同尺寸屏幕预览而已)上面看到刚才添加的红色view没有展示(也可以通过旋转屏幕查看view是否正常显示)。原因是红色view是相对于8Plus设置的frame,也就是view的x坐标是(414-100=314),y坐标(736-100=636),但是4s的屏幕尺寸是320*480,不在屏幕范围内,所以不显示。

修改Autoresizing
我们看到修改尺寸下面有个Autoresizing栏目,光标移到对应位置还会有动画效果。左边有一个可操作区(默认连接左边和上边),里面外围每一条线代表距离父视图是固定间距。如果点亮右边和下边的线,意味着该view距离父视图右边和下边都是固定间距。内围有两条线,水平方向代表宽度是否伸缩,垂直方向代表高度是否伸缩。每改变一次连线状态,都能在右边看到预览动画。
改变连线状态-IB-autoresizing

选中红色view,取消上和左的连接状态,连接右和下,这时候看到在两个不同尺寸屏幕上都正常显示。
连线右和下-IB-autoresizing

选中水平和垂直,宽度和高度将会根据屏幕自动伸缩。
选中水平和垂直-IB-autoresizing

新增一个蓝色view位于屏幕左下角,宽高和红色view相等。
兄弟关系-IB-autoresizing
可以看到,蓝色view和红色view是兄弟关系,但只能设置相对父视图的位置和伸缩尺寸,无法设置兄弟之间的关系。

思考:如果四条线都勾选会怎么样呢?
view会被展示到左上角,因为四条线的相对方向是有互斥性及优先级的,左 > 右,上 > 下。

纯代码

UIView有一个autoresizingMask属性可以设置AutoresizingautoresizingMask是一个UIViewAutoresizing类型的枚举值,默认值是UIViewAutoresizingNone

1
2
3
4
5
6
7
8
9
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0, // 默认值
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 相对于父控件左边距弹性布局(即右边距固定)
UIViewAutoresizingFlexibleWidth = 1 << 1, // 相对于父控件宽度弹性布局(即宽度根据父控件大小自动拉伸)
UIViewAutoresizingFlexibleRightMargin = 1 << 2, // 相对于父控件右边距弹性布局(即左边距固定)
UIViewAutoresizingFlexibleTopMargin = 1 << 3, // 相对于父控件上边距弹性布局(即下边距固定)
UIViewAutoresizingFlexibleHeight = 1 << 4, // 相对于父控件高度弹性布局(即高度根据父控件大小自动拉伸)
UIViewAutoresizingFlexibleBottomMargin = 1 << 5 // 相对于父控件下边距弹性布局(即上边距固定)
};

案例一(场景:红色view位于屏幕右下角且宽高均为100,根据屏幕自适应位置及尺寸)

1
2
3
4
5
6
7
UIView *redView = [[UIView alloc] init];
CGFloat redView_w = 100;
CGFloat redView_h = 100;
redView.frame = CGRectMake(self.view.frame.size.width-redView_w, self.view.frame.size.height-redView_h, redView_w, redView_h);
redView.backgroundColor = [UIColor redColor];
redView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:redView];

代码-autoresizing

案例二(场景:红色view位于屏幕右下角且宽高均为100;蓝色view右边距离红色view的左边间距为30且宽高和红色view相等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
UIView *redView = [[UIView alloc] init];
CGFloat redView_w = 100;
CGFloat redView_h = 100;
redView.frame = CGRectMake(self.view.frame.size.width-redView_w, self.view.frame.size.height-redView_h, redView_w, redView_h);
redView.backgroundColor = [UIColor redColor];
redView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
[self.view addSubview:redView];

UIView *blueView = [[UIView alloc] init];
CGFloat space = 30;
CGFloat blueView_w = CGRectGetWidth(redView.frame);
CGFloat blueView_h = CGRectGetHeight(redView.frame);
CGFloat blueView_x = CGRectGetMinX(redView.frame)-space-blueView_w;
CGFloat blueView_y = CGRectGetMinY(redView.frame);
blueView.frame = CGRectMake(blueView_x, blueView_y, blueView_w, blueView_h);
blueView.backgroundColor = [UIColor blueColor];
blueView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
[self.view addSubview:blueView];

兄弟关系-代码-autoresizing

AutoLayout

如果项目允许可视化编程(Interface Builder),AutoLayout必然会被用到,这也是整个IB的精髓。苹果官方也建议开发者使用AutoLayout进行UI布局,因为它确实很高效。由于使用代码实现AutoLayout会非常繁琐,所以我们一般也只在IB中使用。当然,纯代码方式推荐使用第三方框架Masonry

AutoLayout有两个核心概念:

  • 约束:通过给控件添加约束,来决定控件的位置和尺寸
  • 参照:添加约束时,是依据谁来添加(可以是父控件或者兄弟控件)

自动布局的核心计算公式:obj1.property1 = (obj2.property2 * multiplier) * constant value,先记住公式即可,接下来我们通过案例来慢慢了解他。

官网指南:Auto Layout Guide

Storyboard

打开Storyboard,创建一个view并选中,在操作区下方有一个添加约束的按钮,点击之后会出现约束条件,有一个Contrain to margins复选框,取消勾选(每次都要这样做,为什么?这是苹果官方推荐的边距尺寸,视图和跟控制器的边距是20pt,父子之间是8pt,其实我们没必要使用它)。
autolayout界面

添加约束

约束条件上面的四个边距和autoresizing很相似,默认设置相对于最近一个视图的上下左右边距,下面的就很好理解了,设置宽度和高度。设置完成后一定要点击最后的Add 4 Contraints,这样才能将约束添加到对应的视图上。

可以用鼠标点击对应数值框修改,也可以按顺序使用键盘Tab键,每次值变化后都会默认选中对应约束条件。添加约束完成后会自动更新IB界面,Xcode9之前都需要我们手动更新。
添加约束

约束添加完成,预览发现,不同尺寸或横竖屏展示的效果都一样。
约束完成

注意:约束不能重复添加,否则会报错。比如我们需要把红色view的宽和高修改为150,直接添加一个宽高150的约束。
约束冲突
系统提示约束错误,我们根据系统提示把需要删除的约束删除即可。

修改/更新约束

1. 删除之前约束,添加新的约束

选中操作元素(即红色view),点击约束区Resolve Auto Layout Issues按钮。选择Clear Constraints,这时候就把view的所有约束都清除掉了,添加新的约束即可。
清除约束

清除约束时,慎重点击Clear Constraints,该操作的对象是整个根视图内的所有约束。从上图可以看到,系统已经对这些操作做了分组处理,一般情况下我们只需要关注Selected Views分区就行。

2. 左边栏

展开左栏view的约束,点击需要修改的约束,在右边栏的Constant输入框内输入需要修改的值,回车。
修改约束

3. 双击view的约束线条(不建议,触控范围太小)

双击需要修改的约束线条,在弹出框内修改Constant的值,然后点击回车。
修改约束

4. 右边栏

通过右侧的尺寸区域修改约束条件,两种选择方式选其一即可(不同方式可修改的条件不一样)。
修改约束

5. 更新约束

有时候我们在操作界面拖动了已经添加好约束的视图,发现约束线条颜色变黄,而且左上角多了一个警告按钮,这时候只需要把约束更新下就可以了。
更新约束

6. 约束失效

如果我们需要添加一个新的约束,仅仅是看下效果,又不想删除之前的约束,就可以用这种方法–约束失效。选择待失效的约束,把右边栏的installed复选框取消勾选(默认都是勾选)。如果想要恢复之前的约束,把installed再次选中即可。选中约束直接按delete,约束将会被完全删除。
约束失效

约束参照

在添加约束的时候,我们可以看到约束值的旁边有一个三角按钮,表示有可选项,有哪些内容呢?他们的作用是什么?

新添加一个view,放到右下角,然后开始添加约束条件,点击右边约束的三角按钮看到默认选中了一个选项Safe Area(current distance = 0)。意思很简单,当前这个view的约束条件参照物是安全区域(安全区域是刘海屏的概念—即iPhoneX以上系列,简单的说就是空出上面的状态栏和下面的Home指示条,剩余的就是安全区域),并且目前距离安全区域的右边距是0。
约束参照

例:view展示到屏幕右上角,不同的参照物,可能位置也会不一样。

约束辅助参照对应的还有Top Layout GuideBottom Layout Guide,在Xcode10以上需要手动把Use Safe Area Layout Guides取消(默认勾选),才能显示。具体不再介绍,原理和Safe Area类似。

兄弟之间、父子之间的对齐方式
对齐方式

案例一(兄弟关系-等宽等高)

场景:在Controller中添加两个等宽等高(高度50)的view,左边红色,右边蓝色,父控件左边、右边、下边及连个view之间的间距都是相等的(间距30)。

案例一

操作技巧:

  • command可以选择多个控件(可以设置等宽等高、对齐方式等);
  • 选中控件,按住control,往关联控件内拖就可以设置关联属性;
  • 如果设置左右边距的时候发现找不到红色或蓝色view作为参照,很大原因是因为两个在水平方向上没有交集,这时候拖动任意一个到同一个方向即可。

案例二(兄弟关系-中心对齐)

场景:在Controller中添加一个红色view,左右边距和高度都为50,父视图中心点对齐,一个蓝色view,宽度为红色的一半,高度和红色一致,底部距离红色顶部50。

案例二

操作技巧:

  • 约束添加完成后,如果界面没有变化也没有警告报错,可以尝试主动修改约束的常量值(Constant)。

案例三(父子关系-等宽等高及对齐)

场景:4个view进行2*2排列占满屏幕,每个view都有一个距离自己四边距30的子view。

案例三

操作技巧:

  • 当视图比较多的时候,可以给每个view打上一个标签名,这样就很容易区分;
  • 视图约束参照物默认选择距离最近的一个。

案例四(UILabel内容伸缩)

场景:UILabel根据文字多少自动计算宽高。

  • 没有AutoLayout之前,UILabel的文字内容总是居中显示,导致顶部和底部会有一大片空白区域;
  • 有了AutoLayout只后,UILabelbounds默认会自动包住所有的文字内容,顶部和底部也没有空白区域。
    • 这是因为UILabel设置约束后会根据内容的多少自动计算需要显示多少行,并且根据字体大小计算每行文字的高度,计算完成后会把内容高度赋值给UILabel,这时候看到的效果就是UILabel高度会随着文字的多少自动计算。
  1. 添加一个UILabel到控制器上,如果不加任何约束,文字垂直居中水平居左展示。

  2. 多行文字,也只在UILabel的范围内尽可能的展示(需要设置行数为0,即不限制行数),但仍然显示不全。

  3. UILabel添加居中显示的约束。

  4. UILabel添加约束后和其他控件不太一样,由于没有设置宽度和高度,Xcode也没有报错或警告,仅仅文字由之前的多行变成了单行显示。这是因为UILabel没有设置宽度,系统只能根据当前文字大小无限制的展示一行,所以高度也只是一行文字的高。

    我们可以看到如果不设置宽度,仅设置居中对齐的话,UILabel的X坐标和宽都会随着文字的多少而变化,就算超出屏幕也一样。

  5. 如果我们添加宽度约束,并把约束修改为小于等于300(Less Than or Equal),就会发现UILabel可以正常显示了。
    5.1. 添加宽度约束

5.2. 修改宽度并设置约束关系为小于等于

5.3. 可以自动计算高度

思考:为什么设置Less Than or Equal?设置其他关系就不可以么?

约束关系共有三种:

  • Less Than or Equal 小于等于(<=)
  • Equal 等于(=)
  • Greater Than or Equal 大于等于(>=)

Less Than or Equal的意思是最大值不能超过多少。我们设置了宽度300,如果文字很少的情况下,Label会包裹内容显示;如果文字超过300,就会换行显示。

如果设置为Equal,意思就是宽度只能是300,文字很少情况下,Label不会包裹内容显示,宽度一直是300;文字超过300时,同样还会换行显示的。

如果设置为Greater Than or Equal,意思是宽度最小是300,文字很少情况下,Label不会包裹内容显示,宽度一直是300;文字超过300时,也不会换行。

操作技巧:

  • UILabel包裹内容三要素:
    • 设置约束
    • 设置宽度(小于等于或等于)
    • 不要设置高度

案例五(父视图随子视图内容伸缩 – 朋友圈/微博)

场景:模拟一条微信朋友圈信息。

查看下面的约束,重点是time的顶部约束等于content的底部约束加上10的间距,根视图view的底部和time的底部对齐,并且有8间距。

修改内容,查看效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *contentLabel;
@property (nonatomic) BOOL isChanged;

@end

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.isChanged = !self.isChanged;

NSString *content = @"1024星球";
if (self.isChanged) {
content = @"1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球1024星球";
}
self.contentLabel.text = content;
}

操作技巧:

  • 记住万能公式

约束优先级

如何根据不同情况设置不同的约束?可以设置约束的优先级,系统在IB中给我们提供了三种优先级选择,纯代码方式提供了7个常量,其实我们都可以设置(0, 1000]范围内任意值。

特点:优先级值越高,优先生效。

示例

场景:三个view,并行排列(尺寸、间距都相同),移除中间一个view,最后的view自动往前移动到被删除view的位置。

  1. 添加三个view并设置约束;

  2. 把中间的view移除;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @interface ViewController ()

    @property (weak, nonatomic) IBOutlet UIView *middleView;

    @end

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.middleView removeFromSuperview];
    }
  3. 移除后,右边的view并没有向前移动。当中间的view被移除后,约束就不存了,右边的view约束又参照于中间的view,所以也没有约束了,最后两个都不显示了;

  4. 右边的view添加新的约束(以左边的view为参照)并设置约束优先级;

  5. 约束优先级生效。

操作技巧:

  • 优先级低的约束,会用虚线框连接或包裹

约束动画

正常线性动画只需要变换frame即可,但是如果是约束怎么办呢?苹果规定使用约束后在修改控件尺寸和位置就不能使用frame,应该继续使用约束。

示例

场景:点击屏幕修改view的宽度,动画显示,动画时长持续2s。

  1. 添加view并设置约束;

  2. 修改宽度约束值;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @interface ViewController ()

    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *widthConstraint;

    @end


    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [UIView animateWithDuration:2.0 animations:^{
    self.widthConstraint.constant = 150;
    }];
    }
  3. 动画没有生效;

  4. 使用强制刷新;

    1
    2
    3
    4
    5
    6
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.widthConstraint.constant = 150;
    [UIView animateWithDuration:2.0 animations:^{
    [self.view layoutIfNeeded];
    }];
    }
  5. 动画生效,为什么这样就可以了呢?因为约束的本质也是转化为frame,此时修改约束时,我们并不知道什么时候计算frame,设置layoutIfNeeded后,等待下次屏幕刷新时就会按照动画形式刷新view。

常见警告和错误

  • 警告:控件的frame不匹配所添加的约束

    • 约束和显示不一致
      • 例:约束控件的宽度为100,而控件现在的宽度是110
    • 解决方案
      • 更新约束
  • 错误:

    • 缺乏必要的约束

      • 例:之约束了宽度和高度,没有约束具体位置
      • 解决方案
        • 添加缺失的约束条件
    • 两个约束冲突

      • 例:1个约束控件的宽度为100,1个约束控件的宽度为110
      • 解决方案
        • 删除其中一个约束或使其失效

纯代码

AutoLayout一般用在IB上,纯代码非常繁琐。如果使用AutoLayout则必须放弃frame,因为AutoLayout的底层实现还是设置frame,如果设置了frame会发生很多冲突甚至未知性错误。

AutoLayoutAutoresizing是互斥的,但UIViewAutoresizing功能是默认开启的,如果想用AutoLayout布局UI,则必须把对应控件的translatesAutoresizingMaskIntoConstraints设置为NO。

每一条约束其实就是一个对象,所以用代码创建约束的时候就是创建很多约束对象。

示例

场景:红色view位于屏幕右下角且宽高均为100,根据屏幕自适应位置及尺寸。

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
/* 添加约束方法:对比万能公式理解
* view1:约束的控件1
* attr1:约束的属性(NSLayoutAttribute枚举类型)
* relation:控件1和控件2的约束关系(NSLayoutRelation枚举类型)
* view2:参照物控件2
* attr2:参照控件2的约束属性
* multiplier:约束值得倍数
* c:约束常量值
* @return 返回一个约束对象
*/
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

UIView *redView = [[UIView alloc] init];
redView.translatesAutoresizingMaskIntoConstraints = NO;
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];

NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:100];
[redView addConstraint:widthConstraint];

NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:100];
[redView addConstraint:heightConstraint];

NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0];
[self.view addConstraints:@[rightConstraint, bottomConstraint]];

约束应该添加到哪个视图上面?
添加约束原则:

  • 两个同层级关系的控件,约束添加到他们的父视图上;
  • 两个不同层级关系的控件,添加到他们最近的共同父视图上;
  • 有层级关系的控件,添加到层次较高的父view上。

对比上面的简单视图使用AutoLayout的纯代码方式实现起来很繁琐,代码量也很大,即使约束有冲突编译也不会报错。所以,平时开发尽量使用IB。

VFL

VFL(Visual Format Language),翻译为“可视化格式语言”,是苹果公司为了简化AutoLayout的编码而推出的抽象语言。

VFL只有水平方向(H:Horizontal)和垂直方向(V:Vertical)的约束,在水平方向上控件名后面的常量是宽度,垂直方向代表高度。

格式

  • H:[控件名1(宽)]-间距-[控件名2(宽)]

    • 示例:H:[redView(20)]-10-[blueView(30)]
    • 含义:水平方向上,控件redView的宽是20,控件blueView的宽度是30,他们之间的间距是10。
  • H:[控件名(>=宽度@优先级)]

    • 示例:H:[redView(>=20@700)]
    • 含义:水平方向上,控件redView的宽大于等于20,该约束条件优先级为700(优先级最大值是1000,优先满足优先级高的约束)。
  • H:|-间距-[控件名1(宽)]-[控件名2(>=宽度)]-|

    • 示例:H:|-10-[redView(>=20)]-[blueView(30)]-|
    • 含义:水平方向上,控件redView的宽大于等于20,距离父控件左间距10;控件blueView的宽是30,紧靠redView的右边,距离父控件右间距为0。

示例

场景:红色view位于屏幕右下角(边距均为20)且宽高均为100,根据屏幕自适应位置及尺寸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 添加VFL约束
* format:VFL语句
* options:NSLayoutFormatOptions的枚举,填写一些约束关系条件
* metrics:参数配置,可以把一些常量参数做一个映射,把名称放到VFL中即可,方便维护
* views:控件关系映射,key作为VFL中的控件名,value是控件对象
* @return 返回一个数组
*/
+ (NSArray<NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary<NSString *, id> *)metrics views:(NSDictionary<NSString *, id> *)views;

UIView *redView = [[UIView alloc] init];
redView.translatesAutoresizingMaskIntoConstraints = NO;
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];

NSDictionary *views = @{@"redView" : redView};
NSDictionary *metrics = @{@"space" : @20};

NSString *h_VFL = @"H:[redView(100)]-space-|";
NSArray *h_constraint = [NSLayoutConstraint constraintsWithVisualFormat:h_VFL options:kNilOptions metrics:metrics views:views];
[self.view addConstraints:h_constraint];

NSString *v_VFL = @"V:[redView(100)]-space-|";
NSArray *v_constraint = [NSLayoutConstraint constraintsWithVisualFormat:v_VFL options:kNilOptions metrics:metrics views:views];
[self.view addConstraints:v_constraint];

操作技巧:
如果views的映射一般情况下都和对象的指针名称一致,其实没有必要做映射,使用系统提供的宏定义即可。NSDictionaryOfVariableBindings(...),例如:NSDictionary *views = NSDictionaryOfVariableBindings(redView, blueView)