AutoLayout可以做什么?
iphone,或者ipad屏幕的分辨率存在这多种款式,单纯的依靠frame布局来解决不同屏幕间的适配会给代码增加复杂性。
你可以拿到window的尺寸,然后经过各种繁琐的计算最后确定下合适的尺寸,但是这样会大大增加代码的复杂性,让布局称为一种额外的开销。AutoLayout很好的解决了这一问题。
AutoLayout是什么?
AutoLayout是一种基于约束的,描述性的布局系统。
基于约束:和以往定义frame的位置和尺寸不同,AutoLayout的位置确定是以所谓相对位置的约束来定义的,比如x坐标为superView的中心,y坐标为屏幕底部上方10像素等 ,尺寸也是可以通过相对尺寸调整,比如width为superView width的0.5倍。
描述性:约束的定义和各个view的关系使用接近自然语言或者可视化语言的方法来进行描述 。
布局系统:用来负责界面的各个元素的位置 ,AutoLayout为开发者提供了一种不同于传统对于UI元素位置指定的布局方法。以前,不论是在IB里拖放,还是在代码中写,每个UIView都会有自己的frame属性,来定义其在当前视图中的位置和尺寸。而使用AutoLayout,就变为了使用约束条件来定义view的位置和尺寸 。
在AutoLayout中主要使用的类是NSLayoutConstraint
约束定义了两个视图对象之间的关系,这种关系可以描述为:
y (relation) m * x + b 简记为 y R m * x + b R可以是== >= <=
比如两个视图View A 和View B ,View B的左边起始在View A 右边起始位置偏移 15 的位置
关系可以描述为:View B’s left = View A’s right * 1.0+ 15
又或者View B的宽度是 View A宽度的一半
关系为:View B's width = View A's width * 0.5 + 0
NSLayoutConstraint的初始函数1:
+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;
上面该方法 用英语描述的意思是 view1 的属性 关系 view2的属性的 multiplier 倍数 加上偏移
值得注意的是 view2 可以是nil 这样该公式也就变成 y R b
然后添加约束的对象一般是view1和view2的最近公有父视图
假如给View 1和View 2确定约束关系,那么关系是被添加进View 3。
如果给View 1和View 3确定约束关系,那么关系是被添加进View 3。 (可以自己是自己的父视图)
View 3和View 4不能确定约束关系 因为没有公有父视图。
下面来看一个例子,没有使用frame来确定绝对位置,而是靠的constraints确定的相对关系
效果图:并没有使用任何绝对定位布置完成
附代码 大部分常用语句使用了宏替换
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) UIView *view1;
@property (nonatomic,strong) UIView *view2;
@property (nonatomic,strong) UIView *view3;
@end
@implementation ViewController
#define PRELAYOUT(View) View.translatesAutoresizingMaskIntoConstraints = NO
#define ADDBORDER(View) {View.layer.cornerRadius=10;View.layer.borderWidth=2;View.layer.borderColor=[UIColor blackColor].CGColor;}
#define SETWIDTH(View) [NSLayoutConstraint constraintWithItem:View attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1.0/3.0 constant:0]
#define SETHEIGHT(View) [NSLayoutConstraint constraintWithItem:View attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeHeight multiplier:1.0/12.0 constant:0]
#define ADDCONSTRAINT() [self.view addConstraint:constraint]
- (void)viewDidLoad {
[super viewDidLoad];
//basic init view
self.view.backgroundColor = [UIColor whiteColor];
_view1 = [UIView new];
_view1.backgroundColor = [UIColor orangeColor];
PRELAYOUT(_view1);
_view2 = [UIView new];
_view2.backgroundColor = [UIColor greenColor];
PRELAYOUT(_view2);
_view3 = [UIView new];
_view3.backgroundColor = [UIColor blueColor];
PRELAYOUT(_view3);
[self.view addSubview:_view1];
[self.view addSubview:_view2];
[self.view addSubview:_view3];
NSLayoutConstraint *constraint = nil;
//view1's center is at self.view's (1*center.x,2.0/3.0*center.y)
//view1's centerX = self.view's centerX * 1.0 + 0
constraint = [NSLayoutConstraint constraintWithItem:_view1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0];
ADDCONSTRAINT();
//view1's centerY = self.view's cneterY * 2/3 + 0
constraint = [NSLayoutConstraint constraintWithItem:_view1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:2.0/3.0 constant:0];
ADDCONSTRAINT();
//view1's width is 1/3 self.view's width ,and height as 1/12 self.view's heigth
//view1's width = self.view's width * 1/3 + 0
constraint = SETWIDTH(_view1);
ADDCONSTRAINT();
//view1's height = self.view's heigth * 1/12 + 0
constraint = SETHEIGHT(_view1);
ADDCONSTRAINT();
ADDBORDER(_view1)
//view2 is relate to view 1 and same size as view 1
//view2's centerX = view1's left * 1 - 20
constraint = [NSLayoutConstraint constraintWithItem:_view2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_view1 attribute:NSLayoutAttributeLeft multiplier:1.0 constant:-20];
ADDCONSTRAINT();
//view2's centerY = view1's bottom * 1 + 80
constraint = [NSLayoutConstraint constraintWithItem:_view2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_view1 attribute:NSLayoutAttributeBottom multiplier:1.0 constant:80];
ADDCONSTRAINT();
constraint = SETWIDTH(_view2);
ADDCONSTRAINT();
constraint = SETHEIGHT(_view2);
ADDCONSTRAINT();
ADDBORDER(_view2)
//view 3 is relate to view 2 and same size as view 1
//view3's left = view2's right * 1 + 40
constraint = [NSLayoutConstraint constraintWithItem:_view3 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:_view2 attribute:NSLayoutAttributeRight multiplier:1.0 constant:40];
ADDCONSTRAINT();
//view3's centerY = view2's centerY * 1 +0
constraint = [NSLayoutConstraint constraintWithItem:_view3 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_view2 attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0];
ADDCONSTRAINT();
constraint = SETWIDTH(_view3);
ADDCONSTRAINT();
constraint = SETHEIGHT(_view3);
ADDCONSTRAINT();
ADDBORDER(_view3)
}
@end
NSLayoutConstraint的初始函数2:
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
有一种更简易的方式提供了AutoLayout,那就是VFL(Visual Format Layout)。以单纯的语句抽象出各视图间的关系。
format是 VFL字符串 具体规定了如何布局
opts 则是布局以何种方式对齐
metrics 则是在字符串中出现的常量字典
views 则是字符串中出现的视图字典
VFL字符串的结构可以表示为
(<orientation>:)? (<superview><connection>)? <view>(<connection><view>)* (<connection><superview>)?
?表示括号内内容可以出现0次或者多次
<oreitentation:>? :规定了布局的方向 分别为V:垂直 从上而下 H:水平 从左至右
<superview>: 用"|"表示 表示字符串中出现视图的父视图
<connection>:可以用不写 即为空 为空 :则下一个视图紧贴着之前视图的右边布局
单个"-"表示,表示预留系统默认间隔 子视图之间(siblings)同级之间 间隔8 子视图和父视图之间间隔为 20
用"- n -"表示,n代表了常数 或者常数名,表示视图间间隔n距离
<view>:用"[viewname]" 来表示 视图 用viewname 就是视图的名字
比如H:|-[view1]-20-[view2]|
可以解释为 在水平方向上 从左至右
①view1间隔在父视图左边的默认距离出布局
②view1 和 view2 之间间隔 20个距离
③view2 紧贴父视图的右边
通过以上基本上就可以确定了位置,但是view1 和view2的宽度 是不定的取决于系统
如果你想要view1 至少100宽,至多150
那么就可以写作如下形式
H:|-[view1(>=100,<=150)]-20-[view2]|
又或者你想要view1和view2 一样宽 可以写作
H:|-[view1(view2)]-20-[view2]|
如果要完成和之前类似的布局 用VFL该怎么做呢?
效果图:
代码:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) UIView *view1;
@property (nonatomic,strong) UIView *view2;
@property (nonatomic,strong) UIView *view3;
@end
#define PRELAYOUT(VIEW) VIEW.translatesAutoresizingMaskIntoConstraints = NO
#define ADDBORDER(VIEW) {VIEW.layer.cornerRadius=10;VIEW.layer.borderWidth=2;VIEW.layer.borderColor=[UIColor blackColor].CGColor;}
#define ADDCONSTRAINTS() [self.view addConstraints:constraints]
#define CONSTARINTS(FORMAT) constraints =[NSLayoutConstraint constraintsWithVisualFormat:FORMAT options:0 metrics:nil views:views]
#define VIEWINIT(VIEW,COLOR) {VIEW = [UIView new];VIEW.backgroundColor = COLOR;PRELAYOUT(VIEW);ADDBORDER(VIEW);}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//basic init view
self.view.backgroundColor = [UIColor whiteColor];
VIEWINIT(_view1, [UIColor orangeColor])
VIEWINIT(_view2, [UIColor greenColor])
VIEWINIT(_view3, [UIColor blueColor])
[self.view addSubview:_view1];
[self.view addSubview:_view2];
[self.view addSubview:_view3];
// NSLayoutConstraint *constraint = nil;
NSArray *constraints = nil;
NSDictionary *views = NSDictionaryOfVariableBindings(_view1,_view2,_view3);
CONSTARINTS(@"V:|-180-[_view1(==50)]");
ADDCONSTRAINTS();
CONSTARINTS(@"H:|-130-[_view1]-130-|");
ADDCONSTRAINTS();
CONSTARINTS(@"V:[_view1]-50-[_view2(_view1)]");
ADDCONSTRAINTS();
CONSTARINTS(@"V:[_view1]-50-[_view3(_view1)]");
ADDCONSTRAINTS();
CONSTARINTS(@"H:|-50-[_view2]-40-[_view3(_view2)]-50-|");
ADDCONSTRAINTS();
}
@end
使用VFL可能在精细度上没有前一种方式高,但是优点在于可视直观快速便捷。
在使用代码自动布局的过程中强烈推荐宏定义 这样就可以省去很多事。
也可以大大压缩代码
不过编译时慢,对运行无影响。
这里一个系统内部宏
#define NSDictionaryOfVariableBindings(...) _NSDictionaryOfVariableBindings(@"" # __VA_ARGS__, __VA_ARGS__, nil)
是用来根据视图名字创建视图的字典映射
比如NSDictionaryOfVariableBindings(_view1,_view2,_view3);
所创建出来的字典就是@{"_view1":_view1,"_view2":_view2,"_view3":_view3}
简单但说就是在解析VFL语句的时候 替换掉其中的_view1这些字符的
顺便一说metrics 也是可以这么用的 在metrics字典中存在"width":100,"spacing":20
如果在VFL语句中使用 就是下面的形式
H:[view1(==width)]-spacing-[view2(==width)]
等同于
H:[view1(==100)]-20-[view2(==100)]
VFL和之前约束是等同的,只是提供了一种快速创建约束的方式
最后来讲优先级的问题
enum {
UILayoutPriorityRequired = 1000, // Required
UILayoutPriorityDefaultHigh = 750, // Compression resistance default
UILayoutPriorityDefaultLow = 250, // Compression hugging default
UILayoutPriorityFittingSizeLevel = 50, // System layout fitting size
};
typedef float UILayoutPriority;
优先级可以从1-1000
自动布局总是优先级比较高的布局 我们所创建的约束默认都是1000的优先级。
当两个约束冲突的时候,就会先满足高优先级的。
优先级的数值可以自己定义 比如751 999 等
最后再来一个VFL字符串的总结:
类型 | 格式 | 例子 |
水平,垂直布局 | H: or V: | V:[view1]-15-[view2] |
视图 | [item] | [view1] |
父视图 | | | H:|[view1]| |
关系 | == or >= or <= | H:[view1]-(>=20)-[view2] |
metrics | metric | H:[view1(<=width)] |
无间隔 | [item][item] | H:[view1][view2] |
弹性间隔 | [item]-(>=0)-[item] | [view1]-(>=0)-[view2] |
固定间隔 | [item]-gap-[item] | V:[view1]-20-[view2] |
固定宽高 | [item(size)] | [item(==size)] [view1(50)] |
优先级 | @value | [view1(<=50@20)] |