iOS AutoLayout VFL visual format

http://www.cocoachina.com/ios/20141209/10549.html

本文将通过简单的UI来说明如何用VFL来实现自动布局。在自动布局的时候避免不了使用代码来加以优化以及根据内容来实现不同的UI。

一:API介绍

  1. NSLayoutConstraint API

1
2
3
4
NSLayoutConstraint
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts
metrics:(NSDictionary *)metrics
views:(NSDictionary *)views;

参数介绍:

format:此参数为你的vfl语句,比如:@"H:|-[button]-|"

opts:枚举参数,默认写0,具体跟据你所实现的需求去选择你想要的枚举

metrics:这里是一个字典,当在format中使用了动态数据比如上现这句:@"H:|-[button(==width)]-|",表示这个button的宽度为width,那么这个参数去哪里找呢?就是在这个字典里面找到key对就的值,如果没有找到这个值,app就会crash.

views:顾名思义,这是传所有你在vfl中使用到的view,那在上面这句例子中的应该怎么传呢?结果是这样的:NSDictionaryOfVariableBindings(button).如果你使用到了多个view,就可以这样NSDictionaryOfVariableBindings(button,button1,button3...),这个名字也要跟参数format中的一一对应,缺一不可.

2.UIView API

1
2
UIView
- (void)addConstraints:(NSArray *)constraints;

在上面1中返回值类型是NSArray,而现在这个方法的参数也刚好是一个NSArray类型。那么直接把上一个方法的返回值当作这个方法的参数就可以了。如果你有多个VFL,你也可以利用可变数组( NSMutableArray)把这多个VFL返回的数据拼在一起,然后再调用addConstraints:方法。

二:简单的使用

1.单控件的使用(没有与其他控制有关联,比如空隙等)

新建一个单页面项目Single View Application),在项目里面加上下面这段代码代码

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
27
28
29
30
31
#import "ViewController.h"
@interface ViewController ()
  
@end
  
@implementation ViewController
  
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button=[[UIButton alloc]init];
    [button setTitle:@"点击一下" forState:UIControlStateNormal];
    button.translatesAutoresizingMaskIntoConstraints=NO;
    [button setBackgroundColor:[UIColor blackColor]];
    [self.view addSubview:button];
    NSArray *constraints1=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[button]-|"
                            options:0
                            metrics:nil
                            views:NSDictionaryOfVariableBindings(button)];
      
    NSArray *constraints2=[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[button(==30)]"
                            options:0
                            metrics:nil
                            views:NSDictionaryOfVariableBindings(button)];
      
    [self.view addConstraints:constraints1];
    [self.view addConstraints:constraints2];
     
      
}
  
@end

运行程序,效果图如下:

01222.jpg

可以看到,我们新建的button已经出来,证明上面的自动布局语句(VFL)已经生效。那么我们来详细看看这些语句的意义是什么。

1
2
3
4
NSArray *constraints1=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[button]-|"
                         options:0
                         metrics:nil
                         views:NSDictionaryOfVariableBindings(button)];

这里的意思是:button在水平方向上距离它的superView,左右各20px,比如在这里他的大小就是320-20*2=280.在@"H:|-[button]-|"这个语句中,其中"H:"是表示这是水平方向上的约束,"|"是表示superView,"-"表示一个间隔空间,这个间隔如果是如superView之间的,那么就是20px,如果是两个同级别的view,比如@"[button]-[button1]",那么这里表示的是8px.

1
2
3
4
NSArray *constraints2=[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[button(==30)]"
                         options:0
                         metrics:nil
                         views:NSDictionaryOfVariableBindings(button)];

跟上面有点不同,@"V:|-20-[button(==30)]",其中"V:"中代表这是垂直方向上的约束,"|-20-"这里的意思就是距离头部为20px,相当于y坐标为20。后面的"[button(==30)]",是指定这个button的高度为30px.y坐标固定了,高度固定了,那这个view的约束就完成了。如果你有需要,你的高度值(或者其他同类型的)可以使用>=,==,<=来表示,甚至你可以组合来用,像上面的30,你可以指定一个区别,比如:(>=30,<=40),这同样也是可以的。如果你想表达他的优先级别,可以使用@"V:|-20-[button(==30@1000)]",这个@1000,就是他的级别了。你可以适配XIB或者SB对它的优先级做更多的处理.

PS:值得注意的是,在用代码创建的UIView在,一定要加上下面这句代码

1
button.translatesAutoresizingMaskIntoConstraints=NO;

如果没有上面这一行,你的约束将不生效,控制台会输出一连串的错误.

2:多控件之间关联使用

基于上面的代码上,我们重新加了一段代码,现在的全部代码如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#import "ViewController.h"
@interface ViewController ()
  
@end
  
@implementation ViewController
  
- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *button=[[UIButton alloc]init];
    [button setTitle:@"点击一下" forState:UIControlStateNormal];
    button.translatesAutoresizingMaskIntoConstraints=NO;
    [button setBackgroundColor:[UIColor blackColor]];
    [self.view addSubview:button];
    NSArray *constraints1=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[button]-|"
                           options:0
                           metrics:nil
                            views:NSDictionaryOfVariableBindings(button)];
      
    NSArray *constraints2=[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[button(==30)]"
                            options:0
                            metrics:nil
                            views:NSDictionaryOfVariableBindings(button)];
      
    [self.view addConstraints:constraints1];
    [self.view addConstraints:constraints2];
      
      
    UIButton *button1=[[UIButton alloc]init];
    button1.translatesAutoresizingMaskIntoConstraints=NO;
    [button1 setTitle:@"请不要点击我" forState:UIControlStateNormal];
    [button1 setBackgroundColor:[UIColor redColor]];
    [self.view addSubview:button1];
      
    NSArray *constraints3=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[button1]-|"
                             options:0
                            metrics:nil
                            views:NSDictionaryOfVariableBindings(button1)];
      
    NSArray *constraints4=[NSLayoutConstraint constraintsWithVisualFormat:@"V:[button]-[button1(==30)]"
                            options:0
                            metrics:nil
                            views:NSDictionaryOfVariableBindings(button1,button)];
      
    [self.view addConstraints:constraints3];
    [self.view addConstraints:constraints4];
      
}

运行的效果图如下:

022.jpg

通过代码对比,可以看出,在button1的垂直方向约束上,我们做了一点改变。水平方向上跟button一样,这里就不多作解释。我们来看看垂直方向上的。

1
2
3
4
NSArray *constraints4=[NSLayoutConstraint constraintsWithVisualFormat:@"V:[button]-[button1(==30)]"
                         options:0
                         metrics:nil
                         views:NSDictionaryOfVariableBindings(button1,button)];

VFL语句为:@"V:[button]-[button1(==30)]",这里用到了两个view在VFL语句里面。刚才我们也说到,"-"在同一级别的View上使用的时候表示的间距为8个像素点,整一句的意思就是button1的y坐标离button有8个像素点.在不使用auto layout的时候,可以这样表达CGRectGetMaxY(button.frame)+8.

我再改一下上面这一句VFL

1
2
3
4
NSArray *constraints4=[NSLayoutConstraint constraintsWithVisualFormat:@"V:[button]-[button1(==height)]"
                         options:0
                         metrics:@{@"height":@30}
                         views:NSDictionaryOfVariableBindings(button1,button)];

再次运行,你会发现,效果是一样的。这样你就知道怎么动态去给view加上高度或者宽度,或是其他间距了吧?

那么,如何做到两个View,或是多个View之间等高,或者等宽呢?能用VFL可以做到吗?除了通过上面的直接赋值宽高的数值外,VFL还提供了另外一种写法用于等宽等高上。

还是上面的Demo,我们改一下代码

1
2
3
4
5
6
7
8
9
NSArray *constraints3=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[button1(button)]"
                         options:0
                         metrics:nil
                         views:NSDictionaryOfVariableBindings(button1,button)];
      
    NSArray *constraints4=[NSLayoutConstraint constraintsWithVisualFormat:@"V:[button]-[button1(button)]"
                           options:0
                           metrics:nil
                           views:NSDictionaryOfVariableBindings(button1,button)];

通过@"H:|-[button1(button)]",@"V:[button]-[button1(button)]",这两句就可以轻松实现等宽等高了!

三:最后对格式的字符串作一个总结介绍

功能        表达式

水平方向          H:

垂直方向          V:

Views         [view]

SuperView      |

关系         >=,==,<=

空间,间隙       -

优先级        @value

----------------------------------------------------------------------------

如何使用visualFormat语言纯代码写约束呢?经过本人的学习,写出来与大家分享一下:

使用storyboard可以完成我们的大部分的布局需求,然而storyboard不是万能的,在对于后续的可维护性较高的工程中最好使用手写代码,这就需要我们掌握纯手写约束这项基本能力;

我们在苹果的doc文档里面可以看到一些关于VisualFormat的介绍,但这仅仅是个介绍,我们在实际的应用过程中需要更多的知识:

 

首先看一下,手写约束的效果图:

 

图中的两个按钮要求:

1.两个Button的宽度相等=100

2.两个Button的顶部对齐

3._button1的左边距离父视图为默认的间隔(20)

4.button2左边距离_button1为20,这个20约束的优先级是 750;button2右边距离父视图距离为0

首先,看一下使用VisualFormat实现该效果的代码:

注意:为了更全面的介绍VisualFormat,写法格式可能会有多种!!!

.h

 

  1. //  
  2. //  ViewController.h  
  3. //  AutoLayout2  
  4. //  
  5. //  Created by yb on 15/2/9.  
  6. //  Copyright (c) 2015年 http://blog.csdn.net/yangbingbinga. All rights reserved.  
  7. //  
  8.   
  9. #import <UIKit/UIKit.h>  
  10.   
  11. @interface ViewController : UIViewController  
  12.   
  13.   
  14. @end  


.m

 

 

  1. //  
  2. //  ViewController.m  
  3. //  AutoLayout2  
  4. //  
  5. //  Created by yb on 15/2/9.  
  6. //  Copyright (c) 2015年 http://blog.csdn.net/yangbingbinga. All rights reserved.  
  7. //  
  8.   
  9. #import "ViewController.h"  
  10.   
  11. @interface ViewController ()  
  12. {  
  13.     UIButton *button2; //对应的visualFormat名称是  @"[button2]"  
  14. }  
  15. @property(nonatomic,strong)UIButton *button1;//对应的visualFormat名称是;@"[_button1]",有下划线  
  16. @end  
  17.   
  18. @implementation ViewController  
  19.   
  20. - (void)viewDidLoad  
  21. {  
  22.     [super viewDidLoad];  
  23.     _button1=[UIButton buttonWithType:UIButtonTypeCustom];  
  24.     _button1.backgroundColor=[UIColor blueColor];  
  25.     _button1.translatesAutoresizingMaskIntoConstraints=NO;  
  26.     button2=[UIButton buttonWithType:UIButtonTypeCustom];  
  27.     button2.backgroundColor=[UIColor redColor];  
  28.     button2.translatesAutoresizingMaskIntoConstraints=NO;  
  29.     [self.view addSubview:_button1];  
  30.     [self.view addSubview:button2];  
  31.     NSArray *hCons=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_button1(100)]-50@750-[button2(==_button1)]|" options:NSLayoutFormatAlignAllTop metrics:0 views:NSDictionaryOfVariableBindings(_button1,button2) ];  
  32.       
  33.     [self.view addConstraints:hCons];  
  34.       
  35.     NSArray *vCons=[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(100)-[_button1(100)]" options:0 metrics:0 views:@{@"_button1":_button1,@"button2":button2}];  
  36.     [self.view addConstraints:vCons];  
  37.       
  38.     NSArray *vCons1=[NSLayoutConstraint constraintsWithVisualFormat:@"V:[button2(==50)]" options:0 metrics:0 views:@{@"_button1":_button1,@"button2":button2}];  
  39.     [self.view addConstraints:vCons1];  
  40.       
  41. }  
  42. @end  

说明:

 

1.

  1. _button1=[UIButton buttonWithType:UIButtonTypeCustom];  

 

要使用VisualFormat一定要提前分配好内存,否则会在使用constraintsWithVisualFormat的_view参数,会导致崩溃
2.
_button1.translatesAutoresizingMaskIntoConstraints=NO;
使用AutoLayout和默认的autoSizing有冲突,默认 是YES,使用代码 写约束,一定要设置为NO,关闭自动调整,AutoResizing
3.
  1. NSArray *hCons=[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_button1(100)]-50@750-[button2(==_button1)]|" options:NSLayoutFormatAlignAllTop metrics:0 views:NSDictionaryOfVariableBindings(_button1,button2) ];  
  1. 使用constraintsWithVisualFormat方法创建一个约束数组,默认只有一个元素  
  1. 参数说明:(1)@"H:|-[_button1(100)]-50@750-[button2(==_button1)]|"  
  1. H代表水平方向上,_button1距离 父视图 为 - (子视图与父视图之间默认间隔为20),[_button(100)] 代表,宽度为为100也可以写成[_button(>=100)];  
  1. 50@750,代表 _button1和button2之间的间隔是 50优先级为 750(默认的约束的优先级是1000,所以该约束可能不会使用);[button2(==_button1)]代表 button2的宽度等于_button1的宽度,也可以写成[button2(_button1)]  


(2)options:NSLayoutFormatAlignAllTop
对齐约束,比如上面的约束是对齐约束 ,顶部对齐!
其他对齐方式:
  1. NSLayoutFormatAlignAllLeft      
  1. NSLayoutFormatAlignAllRight     
  1. NSLayoutFormatAlignAllTop       
  2. NSLayoutFormatAlignAllBottom  
  3. NSLayoutFormatAlignAllLeading      
  1. NSLayoutFormatAlignAllTrailing   
  1. NSLayoutFormatAlignAllCenterX      
  1. NSLayoutFormatAlignAllCenterY     
  1. NSLayoutFormatAlignAllBaseline      
  1. NSLayoutFormatAlignAllLastBaseline  
这是一个便利的方式,在添加水平约束的时候,可以添加 垂直方向的;垂直方向上 同理

 

(3)

  1. metrics:0   
  1. 该参数是一个字典:  
  1. 例如  
  1. NSDictionary *metrics=@{@"space":@100}也就是,例如在如下的VisualFormat字符串中使用  @"H:[_button1]-space-[button2]"  
  1. 这样,space这个间隔就可以替换成我们想要的约束了!  
  1. (4)绑定变量  
  1. views:NSDictionaryOfVariableBindings(_button1,button2)] 效果等价于:  
  1. NSDictionary *dict=@{@"_button1":_button1,@"button2":button};  
  1. 注意对应的 _button对象和 button不能为空!  
  1. 系统根据你给的字典,来找到约束对应的对象!  
  1. 技巧:  
  1. 我们也可以使用NSString *visualString=[NSString stringWithFormat:@"H:|[_button1(%g),100.0];来添加 变化的宽度或者其他约束,效果和  metrics类似;对于类似于:@"H:|-[_button1(100)]-50@750-[button2(==_button1)]|"的可视化格式字符串,我将在下一篇文章中详细介绍  

 

posted on 2017-08-21 10:41  oyl  阅读(101)  评论(0)    收藏  举报

导航