iOS 开发实践之Auto Layout(From Vincent Sit)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 开发实践之Auto Layout(From Vincent Sit)相关的知识,希望对你有一定的参考价值。

本文是博主 ios 开发实践系列中的一篇,主要讲述 iOS 中 Auto Layout(自动布局)在实际项目中的使用。

Auto Layout 在 2012 年的 iOS 6 中发布,距今已经 2 年多了,如果从 2011 年在 Mac OS X 上发布的 Auto Layout 开始算起,已经超过 3 年了。如果你的简历上写着 2 年以上工作经验,而竟然不会使用 Auto Layout,真有点不可思议。

本文将会通过若干个 Demo 进行讲解,通过实践来理解 Auto Layout 到底是什么,该如何使用(包括在 Xib 中使用以及手动编码)。

Auto Layout 是什么?

我的理解:Auto Layout 是一种基于约束的布局系统,它可以根据你在元素(对象)上设置的约束自动调整元素(对象)的位置和大小。

官方的说明:

Auto Layout 是一个系统,可以让你通过创建元素之间关系的数学描述来布局应用程序的用户界面。——《Auto Layout Guide

Auto Layout 是一种基于约束的,描述性的布局系统。——《Taking Control of Auto Layout in Xcode 5 - WWDC 2013

这里有几个关键字:

  • 元素 
  • 关系
  • 约束
  • 描述

元素(Element)

低头看看你电脑的键盘,你可以把每一个按键当做一个元素;对于 iOS 系统来说,你可以把桌面上每一个应用图标当做一个元素;对于某一款 iOS 应用来说,你可以把视图中的每一个子视图当做一个元素。

事实上,你也可以把整个键盘、桌面或者视图当做一个元素。

关系(Relation)

元素之间可以有关系。例如在键盘上 Q 键和 W 键之间有关系。是什么关系呢?有很多,例如 Q 键在 W 键的左边,W 键在 Q 键的右边,Q 键和 W 键之间相距 0.5 厘米等等。

不理解?试着把键盘想象成 View,把按键想象成 Button,再思考一遍。

约束(Constraint)

元素之间关系的限制。约束是 Auto Layout 系统中最重要的概念。我们上面提到的 左边右边 以及 相距 0.5 厘米 等这些都是约束,它们限制了元素之间的关系。

描述(Description)

定义约束来限制元素之间的关系。描述定义了元素之间的关系及约束。

继续用键盘举例,Q 键的长宽均为 1 厘米,左边距离键盘的左边缘 10 厘米,上边距离键盘的顶部 5 厘米。这句话就可以定位 Q 键在键盘中的位置,很轻松就可以计算出 Q 键的 frame 为 {{10.0, 5.0}, {1.0, 1.0}}

现在 Q 键的坐标已经确定,那么 W 键的坐标可以这样描述:顶部和 Q 键对齐,大小和 Q 键相等,位于 Q 键右侧 0.5 厘米处。仔细想想,这句话中包含了元素间的关系,关系间的约束,可以直接计算出 W 键的 frame

忘掉传统的 Springs & Struts 布局方式

事实上如果你用传统的设置 frame 的布局方式的思维来理解上面的 Q 键和 W 键的布局也说的通。

因为在 Auto Layout 中,当你描述完之后, Auto Layout 会自动帮你计算出 frame。换句话说,你的描述告诉了 Auto Layout 如何帮你计算出 frame。所以,你也可以理解为你间接的设置了 frame。为什么要这么做呢?为什么不直接设置 frame?这是因为使用 Auto Layout 有很多好处:

  • 多数情况下旋转屏幕不用再做额外的处理
  • 更容易适配不同尺寸的屏幕
  • 上手后布局非常简单容易,布局逻辑更清晰

Auto Layout 和传统布局很大的不同之处在于它是一种相对的布局方式。怎么理解这句话?上面提到

W 键位于 Q 键右侧 0.5 厘米处。

传统的布局无法直接表示,你必须把这种布局手动转换为传统布局代码。例如上面的 Q 键和 W 键的传统布局代码看起来可能是这样:

q.frame = CGRectMake(CGRectGetMinX(keyBoard.frame) + 10.f, CGRectGetMinY(keyBoard.frame) + 5.f, 1.f, 1.f);
w.frame = CGRectMake(CGRectGetMaxX(q.frame) + 0.5f, CGRectGetMinY(q.frame), CGRectGetWidth(q.frame), CGRectGetHeight(q.frame));

使用 Auto Layout 的布局代码看起来像这样:

// 伪代码
q.width = 1.f;
q.height = 1.f;
q.left = keyboard.left + 10.f;
q.top = keyboard.top + 5.f;

w.top = q.top;
w.width = q.width;
w.height = q.height;
w.left = q.right + .5f;

Auto Layout 不仅能轻松表示这种布局,而且相对于传统的布局更清晰简洁易懂,还免费附赠很多优点,有什么理由不使用 Auto Layout 呢?

实践中我发现对于很多新手来说,Auto Layout 这种布局方式比较容易理解接受,相反很多对传统布局很熟练的人却不太容易理解,总是用传统布局的思维来思考,所以如果可能的话,我建议你暂时忘掉传统的布局方式。

Autoresizing Mask

事实上我不打算讲这个东西,以及它和 Auto Layout 的区别和联系。如果你不知道,对学习 Auto Layout 不会有什么影响。

你唯一需要注意的是在使用 Auto Layout 时,首先需要将视图的 translatesAutoresizingMaskIntoConstraints 属性设置为 NO。这个属性默认为 YES,如果你是使用 Xib 的话,这个属性会自动帮你设置为 NO。当它为 YES 时,运行时系统会自动将 Autoresizing Mask 转换为 Auto Layout 的约束,这些约束很有可能会和我们自己添加的产生冲突。

Auto Layout 基础知识

无论是在 Xib 中还是代码中使用 Auto Layout,你都需要了解 Auto Layout 的一些必要知识。这些你现在不理解没有关系,后面我们会详细讲述。

约束 (Constraint)

Auto Layout 中约束对应的类为 NSLayoutConstraint,一个 NSLayoutConstraint 实例代表一条约束。

NSLayoutConstraint 有两个方法,第一个是

+ (id)constraintWithItem:(id)view1
               attribute:(NSLayoutAttribute)attribute1
               relatedBy:(NSLayoutRelation)relation
                  toItem:(id)view2
               attribute:(NSLayoutAttribute)attribute2
              multiplier:(CGFloat)multiplier
                constant:(CGFloat)constant;

不要被这个方法的参数吓到,实际上它只做一件事,就是让 view1 的某个 attribute 等于 view2 的某个 attribute 的 multiplier 倍加上 constant

这里的 attribute可以是上下左右宽高等等。

精简后就是下面这个公式:

view1.attribute1 = view2.attribute2 × multiplier + constant

还有一个参数是 relation,这是一个关系参数,它标明了上面这个公式两边的关系,它可以是小于等于 (≤)等于 (=)大于等于 (≥)。上面的公式假定了这个参数传入的是 =,根据参数的不同,公式中的关系符号也不同。

需要注意的是, 或  优先会使用 = 关系,如果 = 不能满足,才会使用 < 或 >。例如设置一个 ≥ 100 的关系,默认会是 100,当视图被拉伸时,100 无法被满足,尺寸才会变得更大。

例子:

1、我们要实现一个如下图的布局。

技术分享

布局代码如下:

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]];

实现一个如此简单的布局竟然要写这么多的代码,这显然难于推广使用。于是 UIKit 团队发明了另外一种更简便的表达方式进行布局,这个我们后面再讲,现在先看看这段代码。

首先我把 view 的 translatesAutoresizingMaskIntoConstraints 设为了 NO,禁止将 Autoresizing Mask 转换为约束。

然后在设置 viewLeft 这个约束时,attribute 参数使用了 NSLayoutAttributeLeading 而不是 NSLayoutAttributeLeft,这两个参数值都表示左边,但它们之间的区别在于 NSLayoutAttributeLeft 永远表示左边,但 NSLayoutAttributeLeading 是根据习惯区分的,例如在某些文字从右向左阅读的地区,例如阿拉伯,NSLayoutAttributeLeading 表示右边。换句话说,NSLayoutAttributeLeading 是表示文字开始的方向。在英文、中文这种从左往右阅读的文字中它表示左边,在像阿拉伯语、希伯来语这种从右往左阅读的文字中它表示右边。通常情况下,除非你明确要限制在左边,否则你都应该使用 NSLayoutAttributeLeading 表示左边。相对的,表示右边也类似这样。这对于我们的本地化工作有很大的帮助。

然后在设置 viewWidth 和 viewHeight 这两个约束时,relatedBy 参数使用的是 NSLayoutRelationGreaterThanOrEqual 而不是 NSLayoutRelationEqual

因为 Auto Layout 是相对布局,所以通常你不应该直接设置宽度和高度这种固定不变的值,除非你很确定视图的宽度或高度需要保持不变。

如果一定要设置高度或宽度,特别是宽度,在没有显式地设置内容压缩优先级(Content Hugging Priority,后面会讲到)和内容抗压缩优先级(Content Compression Resistance Priority,后面会讲到)的情况下,尽量不要使用 NSLayoutRelationEqual 这种绝对的关系,这会带来许多潜在的问题:

  • 根据内容决定宽度的视图,当内容改变时,外观尺寸无法做出正确的改变
  • 在本地化时过长的文字无法显示,造成文字切断,或文字过短,宽度显得过宽,影响美观
  • 添加了多余的约束时,约束之间冲突,无法显示正确的布局

所带来的问题不仅仅局限与这几条,这里只是简单列出几条。

如何正确的设置宽度或高度?给出一些 Tips:

  • 如果宽度和高度布局可以改变,使用固有内容尺寸(Intrinsic Content Size,后面会讲到)设置约束(即 size to fit size)。
  • 如果宽度和高度布局不可以改变,改变约束的关系为 
  • 调整压缩优先级和内容抗压缩优先级

最后我把所有约束都添加到了 view 的父视图 self.view 上。view 的约束为什么不添加到自身而添加到别的视图上去呢?这是由于约束是根据视图层级自下而上更新的,也就是从子视图到父视图。所以 Auto Layout 添加约束有一套自己的规则,如下:

  • 两个同层级间视图的约束,添加到它们共同的父视图上

技术分享

  • 两个不同层级间视图的约束,添加到它们最近的共同的父视图上

技术分享

  • 两个有层级关系的视图的约束,添加到层次较高的视图上(父视图)上

技术分享

因为我们属于最后一种情况,所以子视图 view 的约束添加到了父视图 self.view 上。

接下来是第二个方法

+ (NSArray *)constraintsWithVisualFormat:(NSString *)format 
                                 options:(NSLayoutFormatOptions)opts 
                                 metrics:(NSDictionary *)metrics 
                                   views:(NSDictionary *)views;

这个方法是我们实际编程中最常用的方法。它会根据我们指定的参数返回一组约束。 这个方法很重要,所以我会详细解释每个参数的用途。

format

这个参数存放的是布局逻辑,布局逻辑是使用 可视化格式语言 (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]];

以上是关于iOS 开发实践之Auto Layout(From Vincent Sit)的主要内容,如果未能解决你的问题,请参考以下文章

iOS之Xcode8 Auto Layout新特性

如何解决官方 iOS 应用开发教程中的 Auto Layout 问题?

WWDC2018 之 高性能 Auto Layout

使用Auto Layout中的VFL(Visual format language)--代码实现自动布局

使用 Auto Layout

iOS屏幕适配方案-Auto Layout