SizeClass Autolayout 在Storyboard或XB上的适配
现在苹果生态圈中的设备尺寸也已经变得种类繁多了,设备种类如下:
iPad:iPad1,iPad2,newPad,iPad4,iPad air,iPad air2,iPad mini1,iPad mini2,iPad mini3,iPad pro;
iPhone:iPhone3Gs,iPhone4,iPhone4S,iPhone5,iPhone5S,iPhone5C iPhone6,iPhone6 Plus,iPhone6S,ipad iPhone6S Plus;
iWatch
屏幕大小:
- iPhone:3.5,4.0,4.7,5.5
- iPad: 7.9,9.7,12.9
- iWatch:1.6,1.8
想必苹果也意识到这一点。都知道苹果是以化繁为简的设计哲学深入人心的,这次再一次证明了。SizeClass是对设备尺寸的一个抽象概念,现在任何设备的 长、宽 被简洁地分为三种情况:普通 (Regular) 、紧密 (Compact) 和 任意(Any) ,这样,根据长和宽不同的搭配就能产生 3 * 3 = 9 种不同尺寸。下图展示个每种情况对应的设备。
以前为不同的iOS设备尺寸或者同尺寸横竖屏不同适配UI,都要根据实际情况而去计算frame。使用Size Classes是根据当前的屏幕size类型而使用Auto Layout方式进行布局了,要摒弃之前计算frame的思路,而改用相对布局的思维去思考(实际上还是要计算frame).
而且Xcode6最大的突破也是这里,不在需要建立不同尺寸的storyboard了,只要建立一个,然后修改其view的size就可以做各种屏幕尺寸的适配,如下:
例如我要做iPad页面设计,就设置成w (Regular)/h(Regular)
然后同样的工程,又要兼容横屏的iPhone6 plus,就可以把view的size class设置成:w (Regular)/h(Compact),然后继续适配
然后当程序跑在不同的设备上,或者设备横屏和竖屏切换,就能显示相应的UI了。
示例:
适配iPhone6,在RootViewController的view上添加一个新的view,让这个新的view无论屏幕横屏还是竖屏时候都距离其superview的边缘50点宽,并且横屏时候为绿颜色,竖屏时候为红颜色。
操作如下:
-
切换size class为wCompact/hRegular模式
并且添加一个view,不用管其frame,并设置其背景色为红色
接下来选中红色的view,然后点击Xcode顶部工具栏的Editor-Pin,然后分别添加红色view相对superview边框的约束(上下左右
添加约束的过程中会看到约束的线是黄颜色,表明当前的约束还不能确定view的frame,需要继续添加,当添加完4个约束后,约束线的颜色是蓝色的,表明当前约束是正确的。
然后选中约束,设定约束的值(我们不是想让新的view距离其superview边界50点宽吗!),4个约束都要设置。
设置完后点击下view会自动更新frame,应该是这样的:
2.切换size class为wRegular/hCompact模式,然后重复第一步中的设置,区别是新添加的view背景颜色设置为绿色。
示例2:
基于iPhone适配界面,添加三个view到rootView上,然后无论横屏还是竖屏,新添加的三个view之间及与屏幕边框的距离都保持不变的间距20点宽,效果如图:
因为要适配iPhone横竖屏,所以修改size class为wCompact/hRegular来适配竖屏:拖拽3个view到rootView上,并设置其背景颜色
为了满足设计要求,要添加如下constraint:
- 设定绿色view距离superview左边距和上边距;
- 设定黄色view距离superview右边距和上边距,相对绿色view的的左边距;
- 设定蓝色view的左边距和右边距和下边距,上边距离绿色view下边的距离;
- 设定绿色view与黄色view等宽高
- 设定蓝色view与绿色view等高
操作如下:
选中绿色view,Eidtor->Pin->Leading Space to Superview给绿色view添加相对其superview的左边距,然后选中constraint,修改约束的值为20,其他constraint以此类推
添加完如图:
其中红色框部分清晰的表达了所添加的constraint;蓝色框部分时添加的constraint,目前为黄色线,表明当前的constraint还不能定位view,当一个view的constraint正确的时候,constraint的颜色会变为蓝色。绿色线框的部分表达了constraint的数值,我们想让边距为20,所以设置数值为20 。wC hR Installed表明当前constraint适用于wC hR这种size class,不适合any any的size class。
添加绿色view与黄色view之前的距离时候,由于是设定两个子view的constraint,所以要选中两个view,然后Editor->Pin ->Horizontal,设定值为20:
同样方法Editor->Pin ->Width Equally,设定绿色view与黄色view等宽度,蓝色view与绿色view等高,结果如图:
但发现constraint颜色仍然后黄色,原因是当前view的位置和constraint希望的不一致,更新下frame(要选中3个view,因为constraint关联3个view)或者点击Document Outline中的黄色小箭头,然后会看到具体的constraint信息来一步步调试,这个也是Xcode6最有突破的地方:
然后效果如图:
然后运行下项目吧,发现确实和预期的一样。然后旋转屏幕,是不是发现横屏时候白了,屏幕什么都没有了?原因是我们仅仅适配的竖屏,横屏还没有适配啊!
修改size class,iPhone4s横屏的size class为wCompact/hCompact,而iPhone6 plus为wReguage/hCompact,那我们不如设置为wAny/hCompact吧!然后安装上边适配竖屏的方式适配横屏。适配好后再次运行,横竖屏都应该是我们想要的了。
小小技巧:
查看不同设备适配情况
Autolayout 纯代码的适配
在 ios6 之前没有AutoLayout布局UI,我们布局UI是基于固定的frame,bound对控件的布局,设置控件的 位置(x,y), 尺寸(width,height)就可以把控件放在相应的位置。
出现Autolayout后,我们用AutoLayout布局控件就要把之前固定设置frame、bound忘记;要想布局显示一个控件,Autolayout以两个词:约束,参照 动态设置一个控件两个东西,位置,尺寸;
小结:
1. 添加约束不宜过多,当添加的约束足以表达该控件的位置与尺寸,就足够了
2. 约束就是对控件的大小或者位置进行约束,参照就是以某个控件的位置进行约束,其实这两者没有明确的分别,它们都可以对控件的位置与尺寸起到作用。
两种语法
1)手动添加约束(苹果官方API)
/**
* 这个是系统默认添加约束的方法,它是NSLayoutConstraint的类方法
*
* @param view1 传入想要添加约束的控件
* @param attr1 传入想要添加约束的方向,这个枚举值有很多,可以自己看看
* @param relation 传入与约束值的关系,大于,等于还是小于
* @param view2 传入被参照对象
* @param attr2 传入被参照对象所被参照的方向,如顶部,左边,右边等等
* @param multiplier 传入想要的间距倍数关系
* @param c 传入最终的差值
*
* @return NSLayoutConstraint对象
*/
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
//一部分NSLayoutAttribute枚举值
NSLayoutAttributeLeft = 1,//控件左边
NSLayoutAttributeRight,//控件右边
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,//控件左边
NSLayoutAttributeTrailing,//控件右边
NSLayoutAttributeWidth,//控件的宽
NSLayoutAttributeHeight,//控件的高
NSLayoutAttributeCenterX,//竖直方向中点
NSLayoutAttributeCenterY,//水平方向中点
这个方法的参数我很想用形象的语言描述出来,但是还是想不出,更多需要大家从下面的代码中传入的参数去体会
//创建redView
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];
//创建redView第一个约束,相对self.view的左边缘间距20
NSLayoutConstraint * redLeftLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeLeftMargin
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0f
constant:20.0];
//只有在没有参照控件的情况下,约束才加到自身,不然加到父控件上
[self.view addConstraint:redLeftLc];
//创建redView第二个约束,相对self。view的底边缘间距20
NSLayoutConstraint *redBottomLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottomMargin
multiplier:1.0f
constant:-20];//由于是redview相对self.view往上减20,所以是-20
//添加约束
[self.view addConstraint:redBottomLc];
//这里直接设置自身宽为50
NSLayoutConstraint * redWLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50.0f];
//由于没有参照物,所以约束添加于自身身上
[redView addConstraint:redWLc];
//创建最后一个约束,自身的高
NSLayoutConstraint * redHLc = [NSLayoutConstraint constraintWithItem:redView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50];
//由于没有参照物,所以约束添加于自身身上
[redView addConstraint:redHLc];
运行的效果图
接下来我们继续增加需求,在红色方块的右边放一个离它20间距,离self.view底部也间距20,宽高相等的蓝色方块
//先创建一个一个蓝色的view添加到视图上,剩下的就是用autolayout来设置它的“frame”了
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
blueView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:blueView];
self.blueView = blueView;
//创建第一个约束,左边间距,由于是想要与红色有20的间距,那么参照参数“toItem”就应该填redView
NSLayoutConstraint *blueLeft = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeRight
multiplier:1.0f
constant:20.0f];
//与其他控件发生约束,所以约束添加到父控件上
[self.view addConstraint:blueLeft];
//现在我们已经可以确定自己水平方向的位置了,还差垂直方向的位置,现在我们来创建第二个约束,参照物依然是红色方块,需求是要离self.view底部20间距,这不是正好和红色一样么,那么我们可以直接与红色方块底部对齐就行了
NSLayoutConstraint *blueBottom = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.0f];//与红色方块底部对齐,倍数1.0f.差值0.0f
//与其他控件发生约束,所以约束添加到父控件上
[self.view addConstraint:blueBottom];
//剩下两个约束差不多,我就一并描述了,它们都以redView为参照,与其等宽等高
NSLayoutConstraint *blueW = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f];
[self.view addConstraint:blueW];
NSLayoutConstraint *blueH = [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeHeight
multiplier:1.0f
constant:0.0f];
[self.view addConstraint:blueH];
接下来看看效果图
小结:
1.其实Autolayout的思想非常简单,刚开始使用的时候不要想着马上一气呵成,最好一个控件一个控件的实现依赖,分别满足其位置与尺寸的需求,如果一下子几个控件一起弄的话,往往大家犯错是犯在约束添多了,而不是添少了。
2.就如上面的例子,很多人会在设置了与红色等高等宽后,还同时去添加顶部对齐与底部对齐,这样高度就重复设置了,他会忽略了,上下同时对齐不仅给予了垂直位置,也给予了高度,所以思路必须清晰!
Autolayout 下添加动画
我将在蓝色方块的右边再加个同样大小的黄色方块,然后,要求点击屏幕,然后蓝色方块被移除,黄色方块替代蓝色方块的位置
还有一个autolayout的知识点:优先级(priority)
//一如往常,先创建黄色View
UIView *yellowV = [[UIView alloc]init];
yellowV.backgroundColor = [UIColor yellowColor];
yellowV.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:yellowV];
//开始创建约束,第一个约束是什么呢?一看就知道是左间距约束啦
NSLayoutConstraint *yellowLeft = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:blueView attribute:NSLayoutAttributeRight
multiplier:1.0f
constant:20];
//与其他控件发生约束,所以约束添加到父控件上
[self.view addConstraint:yellowLeft];
//添加底部约束
NSLayoutConstraint *yellowBottom = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:-20];
//与其他控件发生约束,所以约束添加到父控件上
[self.view addConstraint:yellowBottom];
//这里我直接设置宽高约束了,就省事不加参照控件了
NSLayoutConstraint *yellowW = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50.0f];
[yellowV addConstraint:yellowW];
NSLayoutConstraint *yellowH = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:kNilOptions
multiplier:1.0f
constant:50.0f];
[yellowV addConstraint:yellowH];
运行效果
接下来我再给黄色View添加一个约束,这个约束涉及到优先级,大家注意看代码了哈
//对黄色View添加约束,约束黄色view与红色View的间距为20
NSLayoutConstraint *yellowAnotherLeft = [NSLayoutConstraint constraintWithItem:yellowV
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeRight
multiplier:1.0f
constant:20];
UILayoutPriority priority = 250;//设置优先级
yellowAnotherLeft.priority = priority;
//与其他控件发生约束,所以约束添加到父控件上
[self.view addConstraint:yellowAnotherLeft];
想必大家应该看出些端倪了,我在前面已经给黄色View添加了对蓝色View间距位20的view,现在又给黄色view对红色View添加一个间距20的约束,这很明显是不可能出现的情况,黄色View怎么可能同时做到这两个约束呢,用术语来说就是约束冲突,但是大家注意看这段代码
UILayoutPriority priority = 250;//设置优先级
我给yellowAnotherLeft这个约束添加了优先级,优先级的范围是0~1000,数字越大,优先级越高,在不设置的情况下默认为1000
这说明了,我最后添加的这个约束的优先级是低的,这个约束只有在它的冲突约束被抹掉后,它才能实现
也就是说,我把蓝色view移除后,黄色View相对于蓝色View左间距20这个约束就不成立了,那么黄色view会自动的变为与红色View间距20
让我们最后加几行代码,来实现这个动画吧!
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//先把蓝色方块从父视图上移除
[self.blueView removeFromSuperview];
//动画更新界面
[UIView animateWithDuration:1.0f animations:^{
[self.view layoutIfNeeded];
}];
}
autolayout的动画就是这样实现的,将操作代码走完后,再让动画块去更新界面,动画就出来了,效果如下:
2)VFL(visual format language)
需求:适配iPhone6,在RootViewController的view上添加一个新的view,让这个新的view无论屏幕横屏还是竖屏时候都距离其superview的边缘20
H:|-20-[newView]-20-|
V:|-20-[newView]-20-|
每一个 [] 中表示一个视图或一个控件;V 表示垂直约束,H 表示水平约束,然后两个视图之间的 -0- 的意思就很简单了,意思就是这两个视图之间间隔为0
比如:
V:[view0(67)]-0-[view1(67)]-0-[view2(67)]-0-[view3(67)]-0-[view4(67)]-0-|
self.view.translatesAutoresizingMaskIntoConstraints = NO;
UIView *newView = [UIView new];
newView.backgroundColor = [UIColor greenColor];
[self.view addSubview:newView];
newView.translatesAutoresizingMaskIntoConstraints = NO;
NSMutableArray *constraintArray = [NSMutableArray array];
[constraintArray addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[newView]-20-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(newView,self.view)]];
[constraintArray addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[newView]-20-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(newView,self.view)]];
[self.view addConstraints:constraintArray];
运行效果
第三方库 Masonry
- VFL的约束创建非常宏观,如果既要照顾语法讲解,又要照顾约束理解.
- Masonry的约束添加思维其实与苹果原API的添加思维是相同的,只是Masonry语法更简洁,代码更优美
示例
需求:如下所示
实现如下:
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
UIView *yellow = [[UIView alloc]init];
yellow.backgroundColor = [UIColor yellowColor];
[self.view addSubview:yellow];
UIView *green = [[UIView alloc]init];
green.backgroundColor = [UIColor greenColor];
[self.view addSubview:green];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(0);// 使左边等于self.view的控件,间隔为0;
make.top.equalTo(self.view.mas_top).offset(0); // 使顶部与self.view的控件的间隔为0;
make.width.equalTo(self.view.mas_width).multipliedBy(0.5); // 设置宽度为self.view的一半,multipliedBy是倍数的意思,也就是,使宽度等于self.view宽度的0.5倍
make.height.equalTo(self.view.mas_height).multipliedBy(0.5);// 设置高度为self.view的一半。
}];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.and.height.equalTo(redView); // 跟redview等宽高
make.top.equalTo(redView.mas_top); // 与redview顶部对齐
make.leading.equalTo(redView.mas_right); // 与 redview的间隔为0
}];
[yellow mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.and.height.equalTo(redView); // 跟redview等宽高
make.top.equalTo(redView.mas_bottom); // 与 redview的间隔为0
make.leading.equalTo(redView); // 与redvie左对齐
}];
[green mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.and.height.equalTo(redView); // 跟redview等宽高
make.top.equalTo(yellow.mas_top); // 与yellow顶部对齐
make.leading.equalTo(yellow.mas_right); // 与 yellow的间隔为0
}];
PS:需要下载第三库 Masonry ,并导入 #import "Masonry.h"
Masonry 下的 Autolayout Animation
UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
UIView *greenView = [[UIView alloc]init];
greenView.backgroundColor = [UIColor greenColor];
[self.view addSubview:greenView];
self.greenView = greenView;
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(20);
make.bottom.equalTo(self.view.mas_bottom).offset(-20);
make.width.equalTo(self.view.mas_width).multipliedBy(0.2);
make.height.equalTo(self.view.mas_height).multipliedBy(0.2);
}];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(redView.mas_right).offset(20);
make.bottom.equalTo(self.view.mas_bottom).offset(-20);
make.width.equalTo(self.view.mas_width).multipliedBy(0.2);
make.height.equalTo(self.view.mas_height).multipliedBy(0.2);
}];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(greenView.mas_right).offset(20);
make.bottom.equalTo(self.view.mas_bottom).offset(-20);
make.width.equalTo(self.view.mas_width).multipliedBy(0.2);
make.height.equalTo(self.view.mas_height).multipliedBy(0.2);
make.left.equalTo(redView.mas_right).offset(20).priority(250);
}];
让我们最后加几行代码,来实现这个动画吧!
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//先把绿色方块从父视图上移除
[self.greenView removeFromSuperview];
//动画更新界面
[UIView animateWithDuration:1.0f animations:^{
[self.view layoutIfNeeded];
}];
}
运行效果
总结 Autolayout 使用
-
以后一律使用autolayout吗?除了在storyboard中使用autolayout,代码方式autolayout如何使用?
- 当需要展示的内容很多并且尺寸不固定;
- 程序需支持屏幕旋转(主要是iPad程序,iPhone程序横屏的场景有点非主流);
- 程序通用于iPhone和iPad;
-
使用autolayout 利弊
- 好处:可视化,实现简单功能很节省时间
- 坏处:storyboard 使用 autolayout,移动一个控件就会让弄乱那些约束;
-
autolayout有没有局限性和解决不了的问题?兼容性怎么样?效率怎么样
- autolayout对view transforms支持的不好,这里有帖子详细描述了这个问题
- 至于兼容性,只从iOS6就已经提出了autolayout的概念,大部分用户应该是使用iOS7和iOS8系统,所以兼容性问题不会太大,但size class是iOS8才有的概念,所以还有有一定的适配工作量
参考的帖子
iOS Autolayout解读
iOS Autolayout之Masonry解读
storyboard中autolayout和size class的使用详解