一直想写又不想写。因为近来网上关于Autolayout的文章不在少数,重复相同的内容没什么意思。但是只写不同的地方感觉我也写不出多少,比较心塞。既然坑都占了,那就先意思一下,接下来发现Autolayout其他特性或比较高深的用法会再继续补充。
自iOS6苹果就推出了Autolayout,但那时候很多人都在用AutoresizingMasks,国内对新鲜出土的技术并没有那么热衷,最主要的是改变终归是痛苦的,所以鲜有应用。iOS8之后新推出了iPhone6、iPhone6+及Sizeclass,这时候不得不使用Autolayout了。现在觉得这其实也并不是必须的,代码适配也不是不可以。
现在代码添加约束一般有三种,常规的约束语法、可视化格式语言约束、使用第三方组件。
常规约束
代码基本长这样
高度约束(添加到yellowView身上)
1 | NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:50]; |
间距约束(添加到self.view身上)1
2
3
4
5
6
7
8CGFloat margin = 20;
[self.view addConstraints:@[
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:margin],
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant: - margin],
[NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant: - margin]
]];
用到了一个基本公式1
obj1.property1 = (obj2.property2 * multiplier)+ constant value
multiplier和constant 是向量系数和偏移量,一般还要设置translateAutoresizingMaskIntoConstraints为NO,默认是YES,使得不和系统的自动伸缩约束冲突
可视化格式语言约束
可视化格式语言,即Visual Format Language(VFL),是为了简化Autolayout而推出的抽象语言。
基本长这样1
2
3
4
5
6UIButton *b1 = ...
UIButton *b2 = ...
UIButton *b3 = ...
b1.translatesAutoresizingMaskIntoConstraints = NO;
b2.translatesAutoresizingMaskIntoConstraints = NO;
b3.translatesAutoresizingMaskIntoConstraints = NO;
将传入的对象引用作为value,将引用名变成字符串作为key,生成的字典如: {@”b1”:b1,@”b2”:b2,@”b3”:b3}1
NSDictionary *diction1 = NSDictionaryOfVariableBindings(b1,b2,b3);
为vfl式子中的一些特殊含义的数字做一个名称对照表1
NSDictionary *diction2 = @{@"top":@20,@"left":@20,@"right":@20,@"spacing":@10};
1 | NSString *hVFL = @"|-left-[b1]-spacing-[b2(b1)]-spacing-[b3(b1)]-right-|"; |
部分使用规则如下:
|: 表示父视图
-:表示距离
V: :表示垂直
H: :表示水平
>= :表示视图间距、宽度和高度必须大于或等于某个值
\<= :表示视图间距、宽度和高度必须小宇或等于某个值
== :表示视图间距、宽度或者高度必须等于某个值
|-30.0-[view]-30.0-|: 表示离父视图 左右间距 30
[view(200.0)] : 表示视图宽度为 200.0
|-[view(view1)]-[view1]-| :表示视图宽度一样,并且在父视图左右边缘内
V:|-[view(50.0)] : 视图高度为 50
V:|-(==padding)-[imageView]->=0-[button]-(==padding)-| : 表示离父视图的距离
为Padding,这两个视图间距必须大于或等于0并且距离底部父视图为 padding。[wideView(>=60@700)] :视图的宽度为至少为60 不能超过 700
这里只是基本的部分使用规则,要了解更多请自行查阅。
使用第三方组件
UIView+Autolayout下载地址:https://github.com/ChinaFishNews/UIView-AutoLayout.git
Masonry下载地址 : https://github.com/ChinaFishNews/Masonry.git
在最初的时候使用的是UIView+Autolayout,后来发现Masonry就基本使用Masonry了,这两个用起来都很好上手。
UIView+Autolayout一般用法
直接设置大小1
[_redView autoSetDimensionsToSize:CGSizeMake(200, 200)];
设置除顶部距四周的间隔,然后设置高度1
2
3[_redVeiw autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsMake(20, 20, 20, 20)excludingEdge:ALEdgeTop];
[_redView autoSetDimension:ALDimensionHeight toSize:100];
直接设置四周间隔1
[_redView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
Masonary一般用法
1 | UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10); |
或者直接这样1
2
3[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
1 | make.top.mas_equalTo(42); |
1 | [view1 mas_makeConstraints:^(MASConstraintMaker *make) { |
这只是随便举例,想更详细的了解可以到Github源码处具体学习下。
以上,代码添加约束总体较为繁琐,然而有些时候布局比较繁杂、不方便在Storyboard添加的只能只用代码来添加约束了。现在对我而言基本是一般约束直接在Storyboard上拖拽添加,一些不太方便的需要代码添加约束的使用Masonary。
在Storyboard上添加约束
iOS9之后已经可以使用引用了,把Storyboard拆分成多个。多人合作项目的时候经常会产生冲突,Storyboard的冲突改起来比较酸爽,Sotryboard其实是一个巨大的XML文件,之前每次产生冲突都需要以XML文件的形式打开Storyboard修改冲突。即便现在可以把Storyboard拆分成多个,但有时冲突依然不可避免,XML文件其实也并不可怕,毕竟多改几次就习惯了。
添加约束其实比较降低效率,随着布局的繁杂效率下降得更为明显,只在在硬件提升的同时一般我们体会的并不明显。iOS9之后新加了UIStackView,一个管理视图的容器,这样只需要对外面的这个容器添加约束,里面的只需要设置一些属性,虽然归根结底还是设置约束。虽然UIStackView是iOS9之后,但是网上已经有支持iOS9以下的框架,叫FDStackView,下载地址:
https://github.com/ChinaFishNews/FDStackView.git
下载后导入项目即可,无需做其他配置,即可在iOS9以下使用StackView。
先上个图大家感受一下Autolayout
默认大家都已经可以基本使用Autolayout.
一般在右下角可以方便的添加约束,也有人通过菜单栏中Editor设置,其实也可以按住control键,点击控件拖拽
添加约束后有时想知道在不同设备上的布局,这时候可以选中控制器,改变Size的大小,也可以这样
按住option键点击Main.Storyboard
然后可以添加设备
还有时候想观察视图的层次机构,可以在运行时点击Debug View Hierarchy
其实还可以使用工具Reveal,之前的博文中有讲到。
在添加约束的时候,左侧一般是这样的
为什么有的约束在这个控件下,有的约束在其他控件下呢? 是因为只有设置当前控件宽高的时候约束才会出现在当前控件下,设置和其他控件相对位置、距离的时候,会找到他们最近的父控件,在他们最近的父控件下显示约束。
使用Autolayout的时候,有个属性IntrinsicContentSize,UILabel,UIButton,UIImageView拥有这个属性,可以自己根据内容调整大小,不用设置宽和高。文字有多长就显示多长,图片有多大,就显示多大,对于哪些View有IntrinsicContentSize,苹果给了一张表:
这张表显示,UIView和NSView是没有IntrinsicContentSize的。Sliders只能定义width。Sliders的height拥有IntrinsicContentSize,Labels, buttons, switches,and text fields完全支持,Text viewsand image views,在有内容的时候支持,没有内容的时候不支持。对于没有这个属性的,我们也希望他拥有默认宽高的话该怎么办呢?我们可以重写IntrinsicContentSize方法,这样就可以拥有默认的宽高1
2
3
4- (CGSize)intrinsicContentSize
{
return CGSizeMake(100,100);
}
对于IntrinsicContentSize,Autolayout又把他分成了2个部分:ContentHugging和CompressionResistance。我们点击控件的时候在右侧会发现
ContentHugging是内容凝聚力,优先级是250,表示View的宽度和高度紧靠内容,不让其扩展的力量
CompressionResistance是指压缩阻力,优先级是750,表示当有力量要对其进行压缩的时候,其阻力的大小
对于同一个View,ContentHugging和CompressionResistance不会同时起作用
而我们添加约束的时候是这样的,优先级是1000
当一个label有文字的时候,label会存在一个内容的Size。
如果有外力让其size扩张,ContentHugging会起作用,外力大于ContentHugging的力量,label的size由外力决定,反之,label的Size由内容决定。
如果有外力让其size压缩,CompressionResistance会起作用,外力大于CompressionResistance的力量,label的size由外力决定,反之,label的Size由内容决定。
也就是说如果我们给label加了一个宽度约束,line设为0,我们在给line设置内容的时候,一旦内容size大于其原有大小的时候label的高度就会增加,而当我们只给label添加一个宽度的时候,一旦我们设置的内容size大于其原有宽度的时候label的宽度就会增加。而当我们同时设置宽度和高度约束的时候,无论我们怎么设置内容,其就会被固定在我们设置的这个size里,因为添加约束的优先级默认是1000,优先级最大,默认会执行这个。
constraint的优先级(Priorities),优先级越高,力量越大,越被优先执行。系统的优先级由1~1000的数字表示,值越大,优先级越高。NSLayoutConstraint中一共定义了4种比较常用的优先级1
2
3
4
5
6
7
8
9typedef float UILayoutPriority;
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000;
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750;
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250;
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50;
UILayoutPriorityRequired: 必须级别优先级,值为最高值1000,一般平时定义约束,默认都是这个优先级。
UILayoutPriorityDefaultHigh: 高优先级,值为750,CompressionResistance的默认优先级是这个。
UILayoutPriorityDefaultLow: 低优先级,值为250,ContentHugging的默认优先级是这个
UILayoutPriorityFittingSizeLevel: 极低的优先级,让系统估算Size的时候使用,不适合做约束
知道了各个属性的默认优先级之后,就可以解释为什么一般情况我们给Lable设置Size约束之后,Label由我们设置的Size决定,而不是由其内容决定。因为我们没有特意设置优先级,用的都是默认优先级。Size约束的优先级比CompressionResistance和ContentHugging的优先级高。如果我们想让Label由内容决定,我们可以不设置Size约束或者调低自己Size约束的优先级。
上面参考了网上的一篇文章,讲的很赞,并且给了一个案例。
假设有2个Label,并列放着,他们都是使用IntrinsicContentSize自动根据文字适应宽度。效果如图所示:
那么我们设置一个优先级为500宽度为100的约束(100小于Label2的宽度,大于Label1的宽度)
结果是2个Label都变成100的宽度,还是都保持原来的宽度不变?还是一个变成100,一个保持原来的宽度?我们Run一下:
Label1变成了100,Label2还是原来的宽度,为什么呢?
Label1的IntrinsicContentSize宽度比100小,所以当添加一个宽度为100的约束时,ContentHugging在起作用。ContentHugging的优先级为250。宽度为100的约束优先级为500大于ContentHugging。所以宽度为100.
Label2的IntrinsicContentSize宽度比100大。所以当添加一个宽度为100的约束时,CompressionResistance在起作用,CompressionResistance的优先级为750。宽度为100的约束优先级为500小于CompressionResistance。所以宽度还是IntrinsicContentSize的宽度
正常情况下我们在设置Label的时候,给它加个宽度约束,运行是这样的
款读设置的是100,高度没有设置,内容过多的时候回自动自适应,不用使用代码计算size。但是我们设置宽度是100,但是内容太少的时候右边会有空白,这时候怎么办呢?我们可以将宽度设为less than or equal 100,这样在内容少的时候就是这样的
这样就解决了内容少而右边有空白的问题。有时候可能没有内容,但是我们又希望它占位,这时候该怎么办呢?我们可以这样设置,即便在没有内容的时候依然在占位
使用的过程中被还会有一些其他的问题
对于Autolayout,制作动画和以往也有些不同,使用Frame的时候,我们做动画一般都调用-animateWithDuration:animations:方法,在animations的block里面调整Frame即可,使用Autolayout之后,由于Autolayout是延迟布局的,并不是约束更新之后就立刻布局,所以大家可以发现。在-animateWithDuration:animations方法里面修改约束是不能实现动画的。而使用Autolayout该怎么做呢? 应该是在block外改变约束,在block内调用layoutIfNeeded方法。
Autolayout自动布局是在UIView的layoutSubviews中,所以TableView上的子view(如:cell,headerView,footerView)使用了Autolayout,tableView在布局的时候调用layoutSubviews,就会抛出异常。直接对TableView使用Autolayout是不会有问题的,TableView是否调用layoutSubviews在于他上面的子view是否使用Autolayout,而不是他本身。
正确的做法是:如果是cell,我们经常使用[cell addSubview:view]再对view做一个相对cell的约束,这时候就会出现问题。解决方案就是使用[cell.contentView addSubview:view]。我们约束是对cell的contentView添加,跟cell无关。tableView就不会调用layoutSubviews了。
如果是headerView或者footView。解决方案是直接使用frame,或者自己定义一个类似Cell的contentView的view,子view相对contentView布局使用Autolayout,contentView对headerView布局使用frame。使用中还会发现一个特殊的控件,UIScrollView,因为contentSize跟着里面的子view发生变化。正确的做法是给UIScrollview添加一个ContentView,设置edge均为0,如果垂直滚动那就设置一个宽度,反之设置一个高度约束。
使用Autolayout之后,系统中多了一个更新约束的方法updateConstraints。updateConstraints方法仅用于提升性能。当你更新大量约束,发现由于约束太多,布局有点卡。这时候你可以使用updateConstraints,因为在updateConstraints中更新约束会批量操作,能获得更好的性能。
以上,只是Autolayout部分用法,具体的需要在项目中体验,注意点比较多。
就先到这里,今天妇女节,祝广大女同胞节日快乐。觉得应该放半天假,然而这么人性化得公司毕竟不多!
如有任何疑问或问题请联系我:fishnewsdream@gmail.com,欢迎交流,共同提高!
Objective-C/Swift技术开发交流群201556264,讨论何种技术并不受限,欢迎各位大牛百家争鸣!
微信公众号OldDriverWeekly
,欢迎关注并提出宝贵意见
老司机iOS周报,欢迎关注或订阅
刚刚在线工作室,欢迎关注或提出建设性意见!
刚刚在线论坛, 欢迎踊跃提问或解答!
如有转载,请注明出处,谢谢!