VFL简易教程及UI调试技巧


## 1.背景

很多时候需要使用代码来写约束，例如一些用代码写的自定义的控件及其子控件可能天生不支持自动布局，而即便将自定义控件拖到storyboard上，也无法保证内部的view可以自动布局，因而此时只能使用代码添加约束的方式对控件及子view进行约束。
然而，如果使用`[NSLayoutConstraint  constraintWithItem ...]`的方式，需传递很多参数，代码写起来会非常繁琐。幸好苹果提供了另一个方案，VFL，相比前者代码量会减少很多，两种方式的对比见附录1.

## 2.VFL是什么

VFL全称是`Visual Format Language`，苹果提供的一套比较形象的描述约束的语言。

2.VFL语法示例
以下将直接用几个简单的例子来讲解VFL的语法，方便快速上手。
2-1.宽度约束
<img src="https://qiita-image-store.s3.amazonaws.com/0/106852/a6f2100b-460d-639d-fa98-a3f8243fce92.png" width=300px>

VFL:
>[testview(==200)]

注:被约束的view要用[]包起来

2-2.间距约束
<img src="https://qiita-image-store.s3.amazonaws.com/0/106852/dcb03cb0-0c8d-9955-4ab3-81a13ce30f88.png" width=300px>
VFL:
>[view1]-10-[view2]

注:两个view中间的数字即为间距大小，看起来是不是很形象？

2-3.leading ,trailing约束
<img src="https://qiita-image-store.s3.amazonaws.com/0/106852/b1c76b23-9e9b-4b7b-cb27-86129e2fa7fb.png" width=300px>
VFL:
>|-10-[subview]-10-|

注：边界用|来表示，是不是很形象？

2-4.垂直方向约束
<img src="https://qiita-image-store.s3.amazonaws.com/0/106852/718598dd-ae99-cedd-9086-57aeb1d966cf.png" width=300px>
>V:|-30-[subview]-30-|

注：垂直方向的约束前面要加V:
   水平方向前面一般要加H: 不加的话系统会默认为水平方向

2-5.完整的示例：
<img src="https://qiita-image-store.s3.amazonaws.com/0/106852/69f56951-aa03-8298-c099-7e508b456fcc.png" width=300px>
VFL:
>H:|[subview]|
>V:|[subview]-10-|

注：当边距为0时，可以省略.
即：
>|[subview]|

＝＝
>|-0-[subview]-0-|

## 3.代码中调用示例

本章节以上记2-5为例，演示下在代码中使用VFL的一般方法.
在`Objective-C`代码中使用VFL的一般步骤如下：

  1. 去除view的系统默认的约束限制
 translatesAutoresizingMaskIntoConstraints=NO
  2. 声明一个包含VFL语言的字符串
  3. 生成上记VFL所指定的view和变量的Dictionary
  4. 根据上面的VFL字符串和变量生成约束
  5. 激活约束
  
代码如下：

```objectivec
    UIView * subview=[UIView new];
    [self.view addSubview:subview];
    subview.backgroundColor=[UIColor grayColor];
    
    //1. Deactive the system default constraint
    subview.translatesAutoresizingMaskIntoConstraints=NO;
    
    //2. Define horizon/vertical VFL Strings
    NSString * h_vfl=@"H:|[subview]|";
    NSString * v_vfl=@"V:|-0-[subview]-vdistance-|";
    
    //3. Generate views and variables dictionary using in the VFL.
    NSNumber *vdistance = [NSNumber numberWithInt:10]; 
    NSDictionary *viewsDic= NSDictionaryOfVariableBindings(subview);
    NSDictionary *varDic= NSDictionaryOfVariableBindings(vdistance);
    
    //4.Generate vertical and horizon constraints
    NSArray *h_cons=[NSLayoutConstraint constraintsWithVisualFormat:h_vfl
                                                            options:0
                                                            metrics:nil
                                                              views:viewsDic];
    
    NSArray *v_cons=[NSLayoutConstraint constraintsWithVisualFormat:v_vfl
                                                            options:0
                                                            metrics:varDic
                                                              views:viewsDic];
    
    //5.Activate Constraints
    [NSLayoutConstraint activateConstraints:h_cons];
    [NSLayoutConstraint activateConstraints:v_cons];
    
```

2-5步也可以简写为为如下：

```objectivec
    [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|"
                                                                                    options:0
                                                                                    metrics:nil
                                                                                      views:@{@"subview":subview}]];
    [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subview]-vdistance-|"
                                                                                    options:0
                                                                                    metrics:@{@"vdistance":@(10)}
                                                                                      views:@{@"subview":subview}]];
```

## 4.调试技巧

### 4.1.使用xcode的ui调试工具

1. 进入UI调试模式

 在程序运行时,点击xcode的<img src="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/Art/Screen%20Shot%202015-08-26%20at%2011.35.43%20AM.png">图标，即可进入ui调试模式.
 如下图：
<img src="https://qiita-image-store.s3.amazonaws.com/0/106852/f481e616-d27d-6a8f-28c5-75f7abaaf068.png" width=400px>
 在下面的lldb中可以执行lldb命令来查看view信息.
 例如：

 ```text
 (lldb) po 0x7f875ac36e80
 <UIView: 0x7f875ac36e80; frame = (92 70; 240 128); autoresize = RM+BM; layer = <CALayer:  0x7f875ac2f8e0>>
 ```

1. “Show clipped content”,"Show constraint"
 点击调试界面的“Show clipped content”,能显示偏移到屏幕外的view.
 点击调试界面的"Show constraint",可以显示每个view的相关约束，用来辅助定位约束是否存在问题.
 如下图：
<img src="https://qiita-image-store.s3.amazonaws.com/0/106852/a9a66351-9e77-5403-efde-67130febe945.png" width=400px/>

### 4.2.处理约束相关的日志
>
> Unable to simultaneously satisfy constraints.
> Probably at least one of the constraints in the following list is one you don't want. Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
...
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

当遇到这种日志时，一般的做法如下：
1.使用xcode ui调试工具暂停程序
2.在从上面的日志中找到相应的出错的view的地址,例如0x123.
3.在xcode的lldb console里执行改view的下记方法进行排查:

- po [0x123 recursiveDescription]

    以树形结构打印该view及子view的frame信息.

```text
<UIView: 0x7fd25be264a0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7fd25be20680>>
   | <UIView: 0x7fd25be2adc0; frame = (92 70; 240 128); autoresize = RM+BM; layer = <CALayer: 0x7fd25be23dd0>>
   | <_UILayoutGuide: 0x7fd25be2bf30; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7fd25be206a0>>
   | <_UILayoutGuide: 0x7fd25be2c8f0; frame = (0 568; 0 0); hidden = YES; layer = <CALayer: 0x7fd25be2ac40>>
   | <UIView: 0x7fd25be29c20; frame = (0 548; 310 20); layer = <CALayer: 0x7fd25be0a3c0>>
   | <UIView: 0x7fd25be2dbf0; frame = (320 548; 0 20); layer = <CALayer: 0x7fd25be2aae0>>
```

- po [0x123 _autolayoutTrace]

    打印该view相关的autolayout信息（提供定位约束问题的重要信息）

```text
•UIWindow:0x7fd25be26190 - AMBIGUOUS LAYOUT
|   •UIView:0x7fd25be264a0
|   |   *UIView:0x7fd25be2adc0
|   |   *_UILayoutGuide:0x7fd25be2bf30
|   |   *_UILayoutGuide:0x7fd25be2c8f0
|   |   *UIView:0x7fd25be29c20- AMBIGUOUS LAYOUT for UIView:0x7fd25be29c20.Width{id: 31}
|   |   *UIView:0x7fd25be2dbf0- AMBIGUOUS LAYOUT for UIView:0x7fd25be2dbf0.minX{id: 30}, UIView:0x7fd25be2dbf0.Width{id: 34}

Legend:
 * - is laid out with auto layout
 + - is laid out manually, but is represented in the layout engine because translatesAutoresizingMaskIntoConstraints = YES
 • - layout engine host
```

- po [0x123 constraintsAffectingLayoutForAxis:0]

    打印该view相关的所有的水平方向的约束.参数传1的话，打印所有垂直方向的约束

```text
<__NSArrayI 0x7fd25be21770>(
<NSLayoutConstraint:0x7fd25be2ec20 H:|-(0)-[UIView:0x7fd25be29c20]   (Names: '|':UIView:0x7fd25be264a0 )>,
<NSLayoutConstraint:0x7fd25be2ee10 H:[UIView:0x7fd25be29c20]-(10)-[UIView:0x7fd25be2dbf0]>,
<NSLayoutConstraint:0x7fd25be2ee60 H:[UIView:0x7fd25be2dbf0]-(0)-|   (Names: '|':UIView:0x7fd25be264a0 )>,
<NSAutoresizingMaskLayoutConstraint:0x7fd25bc2d470 h=-&- v=-&- 'UIView-Encapsulated-Layout-Left' H:|-(0)-[UIView:0x7fd25be264a0]   (Names: '|':UIWindow:0x7fd25be26190 )>,
<NSLayoutConstraint:0x7fd25bc22990 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7fd25be264a0(320)]>
)
```

- po [0x123 hasAmbiguousLayout]
  
    判断是否存在有歧义的约束

## 附

### 1.使用VFL跟不使用的区别

Visual Format Language使用的写法:

```objectivec
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-40-[view1(==240)]-20-[view2(==240)]-40-|"
                                                                            options:0
                                                                            metrics:0
                                             views:NSDictionaryOfVariableBindings(view1, view2)]];
                                                                                             
```

Visual Format Language未使用的写法:

```objectivec
[self.scrollView addConstraint:
     [NSLayoutConstraint constraintWithItem:view1
                                  attribute:NSLayoutAttributeLeft
                                  relatedBy:NSLayoutRelationEqual
                                     toItem:self.scrollView
                                  attribute:NSLayoutAttributeLeft
                                 multiplier:1.0
                                   constant:40]];

[self.scrollView addConstraint:
     [NSLayoutConstraint constraintWithItem:view1
                                  attribute:NSLayoutAttributeWidth
                                  relatedBy:NSLayoutRelationEqual
                                     toItem:nil
                                  attribute:NSLayoutAttributeWidth
                                 multiplier:1.0
                                   constant:240]];

[self.scrollView addConstraint:
     [NSLayoutConstraint constraintWithItem:view2
                                  attribute:NSLayoutAttributeLeft
                                  relatedBy:NSLayoutRelationEqual
                                     toItem:view1
                                  attribute:NSLayoutAttributeRight
                                 multiplier:1.0
                                   constant:20]];

[self.scrollView addConstraint:
     [NSLayoutConstraint constraintWithItem:view2
                                  attribute:NSLayoutAttributeWidth
                                  relatedBy:NSLayoutRelationEqual
                                     toItem:nil
                                  attribute:NSLayoutAttributeWidth
                                 multiplier:1.0
                                   constant:240]];

[self.scrollView addConstraint:
     [NSLayoutConstraint constraintWithItem:view2
                                  attribute:NSLayoutAttributeRight
                                  relatedBy:NSLayoutRelationEqual
                                     toItem:self.scrollView
                                  attribute:NSLayoutAttributeRight
                                 multiplier:1.0
                                   constant:40]];
```

### 2.参考链接

<https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/index.html#//apple_ref/doc/uid/TP40010853-CH7-SW1>