最近看MBProgressHUD源文件,发现适配都是用的代码约束,之前我一直用的StoreBoard适配,借此机会顺便学习了使用AutoLayout进行代码适配,在这里记录整理下来,方便自己和有需要的朋友提供一个参考,以下内容难免有错误之处,还请看到的朋友帮我及时更正。
用到的方法
代码适配用到的方法主要就是两个:
/**
* 正常方式约束,需传入以下几个参数
*
* @param view1 要进行约束的控件
* @param attr1 约束属性(枚举类型),要约束控件什么属性,比如上\下\左\右\中心点..
* @param relation 约束关系(枚举类型),即参照控件之间的关系,大于\小于\等于..
* @param view2 参照控件,即view1的约束要参照的view
* @param attr2 要参照view2哪个属性,枚举类型
* @param multiplier 乘数
* @param c 常量
*
* @return 返回一个NSLayoutConstraint对象
*/
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(nullable id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c;
/**
* VFL字符串方式约束
*
* @param format 约束的VFL格式字符串
* @param opts 约束属性,枚举类型
* @param metrics 参数`format`里边的属性所对应的字典
* @param views 参数`format`里边的控件对应关系说明
*
* @return 返回一个NSLayoutConstraint数组
*/
+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format
options:(NSLayoutFormatOptions)opts
metrics:(nullable NSDictionary *)metrics
views:(NSDictionary *)views;
这样看都太抽象,看下具体到视图控件上怎么设置约束。
苹果在计算计算某个视图的约束的时候,有个万能公式,这个公式是这样的:
视图1
某个属性
= (视图2
某个属性
)*乘数
+ 常量
,上边方法一就是根据这个公式来的,或者说,方法一可以这样理解。
设置约束:使用方法一
创建两个UIView对象
// 蓝色视图
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
// 红色视图
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
// 设置blueView约束
NSMutableArray *blueViewConstrains = [NSMutableArray array];
// 设置blueView上边距,这句话意思是:blueView 上边距 = (self.view 上边距) * 1 + 50
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.f constant:50.f]];
// 设置blueView左边距,这句话意思是:blueView 左边距 = (self.view 左边距) * 1 + 20
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.f constant:20.f]];
// 设置blueView上边距,这句话意思是:blueView 右边距 = (self.view 右边距) * 1 + 20
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.f constant:-20.f]];
// 设置blueView高度,这句话意思是:blueView 高度 = 30
[blueViewConstrains addObject:[NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:30.f]];
[self.view addConstraints:blueViewConstrains];
// 设置redView约束
NSMutableArray *redViewConstrains = [NSMutableArray array];
[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeBottom multiplier:1.f constant:20.f]];
[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.f constant:20.f]];
[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.f constant:-20.f]];
[redViewConstrains addObject:[NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:30.f]];
[self.view addConstraints:redViewConstrains];
运行代码:
发现这时候模拟器上并没有显示任何内容,并且控制台打印了一大推东西,这是因为我们没有给blueView
和redView
关闭translatesAutoresizingMaskIntoConstraints
属性,在上边代码中加上两句话:
blueView.translatesAutoresizingMaskIntoConstraints = NO;
redView.translatesAutoresizingMaskIntoConstraints = NO;
运行程序:
可以看出,现在不管是横屏还是竖屏,我们都达到了想要的效果。
设置约束:使用方法二
我们还是分别创建一个蓝色视图,一个红色视图
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
blueView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:blueView];
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
// 设置蓝色视图约束关系
NSMutableArray *blueViewConstrains = [NSMutableArray array];
[blueViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[blueView]-20-|" options:0 metrics:nil views:@{@"blueView" : blueView}]];
[blueViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[blueView(30)]" options:0 metrics:nil views:@{@"blueView" : blueView}]];
[self.view addConstraints:blueViewConstrains];
// 设置红色视图约束关系
NSMutableArray *redViewConstrains = [NSMutableArray array];
[redViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[redView]-20-|" options:0 metrics:nil views:@{@"redView" : redView}]];
[redViewConstrains addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[blueView]-20-[redView(==blueView)]" options:0 metrics:nil views:@{@"blueView":blueView, @"redView":redView}]];
[self.view addConstraints:redViewConstrains];
先不看为什么上边代码里边一大堆字符串是什么意思,我们先来运行下程序,发现和使用方法一
设置约束的效果是相同的,说明我们设置的约束没问题。
下边让我们细细说下上边几句约束代码每句话什么意思:
/**
* 参数一字符串是VFL语言约束字符串,这个字符串我们可以理解为视图控件的一种摆放方式
*
* H: 表示水平方向
* - 表示间隔距离
* 左边|表示屏幕最左边
* blueView是蓝色控件名字,用[]括起来表示这是个视图。在实际中我们最好保持这个名字和控件名字相同
* 右边|表示屏幕最右边
*
* 把这几个参数表达的内容连接起来就是:blueView距离屏幕最左边20 距离最右边20 中间是blueView
*
* options参数表示对其方式,顾名思义就是这个控件要靠哪边对其,枚举类型
* metrics参数表示VFL语言约束字符串里边用到的属性的属性名和属性值
* views参数表示VFL语言约束字符串里边控件名称对应关系,比如blueView就对应我们创建的blueView这个控件
*/
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[blueView]-20-|" options:0 metrics:nil views:@{@"blueView" : blueView}]
/** 垂直方向上的约束:blueView距离屏幕上边距30,并且blueView高度为30 */
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[blueView(30)]" options:0 metrics:nil views:@{@"blueView" : blueView}]
/** 水平方线上:redView距离距离屏幕左边20,右边20。这样就保证设置了redView的x和宽度 */
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[redView]-20-|" options:0 metrics:nil views:@{@"redView" : redView}]
/** 垂直方向上:redView距离blueView20,并且redView高度=blueView高度。这样就保证设置了redView的y和高度 */
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[blueView]-20-[redView(==blueView)]" options:0 metrics:nil views:@{@"blueView":blueView, @"redView":redView}]
这里有个坑要注意,如果我们要设置redView
宽度为blueView
宽度一半,那么上边代码该怎么写呢?有人可能会说了,在redView
的@"V:[blueView]-20-[redView(==blueView)]"
把这句代码改成@"V:[blueView]-20-[redView(==blueView*0.5)]"
不就行了么,但是实际可以么?修改之后运行发现程序崩溃,也就是说系统不识别这种设置约束的方法
查看苹果API,发现有这样一段话:
看到了吧,如果我们要设置这样的属性,必须要使用方法一,这个问题一定要注意。
tips
对于上边两个方法中最后一个参数,推荐使用NSDictionaryOfVariableBindings(..)这个宏,这个宏可以生成一个变量名到变量值映射的Dictionary。比如NSDictionaryOfVariableBindings(view1, view2)
将会生成一个{@"view1" = view1, @"view2 = view2}
的Dictionary。
几个值得注意的地方
- 使用AutoLayout进行适配,请不要设置要适配的控件视图的Frame属性,即使设置了这个属性也不会起作用;
- 务必对控件的
translatesAutoresizingMaskIntoConstraints
属性设置为NO,否则可能导致诸多意想不到的情况; - 给某个属性添加约束,一定要先把这个控件添加到视图上,否则程序会崩溃,记住是一定!一定!一定!
- 方法一和方法二即可互为补充,又可以互为替代,实际开发中可以结合使用;
- 如果要写框架,并且涉及到适配的时候,为了代码可维护性和效率,尽量使用代码进行适配而不是StoreBoard;
- 我们再给控件设置frame的时候,要保证x、y、宽度和高度都要设置,设置约束也是,必须要保证x、y、宽度和高度上都要设置;
约束应该添加到哪个视图呢?
-
对于两个同层级view之间的约束关系,添加到它们的父view上,例如上边例子中,约束添加到self.view上
-
对于两个不同层级view之间的约束关系,添加到他们最近的共同父view上
对于有层次关系的两个view之间的约束关系,添加到层次较高的父view上
对于比如长宽之类的,只作用在该view自己身上的话,添加到该view自己上
几个关于AutoLayout值得一看的Blog
- AutoLayout苹果官方文档
- VFL苹果官方说明文档
- 学习AutoLayout(VFL)
- 史上比较用心的纯代码实现 AutoLayout
- 使用Auto Layout中的VFL(Visual format language)--代码实现自动布局
- AutoLayout视频学习资料,需要的请给我留言要密码