×

一篇文章详解iOS之AutoResizing、AutoLayout、sizeClass来龙去脉

96
VV木公子
2016.05.15 20:13* 字数 9108

前言

iPhone自诞生以来,随着其屏幕尺寸不断的多样化,屏幕适配的技术一直在发展更新。目前,iOS系统版本已经更新到9.3,XCode的最新版本已经是7.3,仅iPhone历史产品的尺寸就已经有4种:3.5英寸、4.0英寸、4.7英寸、5.5英寸。最近,iPhone家族又诞生一款iPhoneSE,鉴于这款iPhoneSE的屏幕尺寸和iPhone5S的尺寸一模一样——同样是4.0英寸,广大iOS开发者可算是松了口气,不然iOS的屏幕尺寸真的是越来越让人眼花缭乱。
按照时间顺序,屏幕适配是这样发展的:纯代码计算frame-> autoresizing(早期进行UI布局的技术,仅适用于约束父子控件之间的关系)->AutoLayout(iOS6/2012年、iPhone5被引入,比autoresizing更加高级,旨在替代autoresizing,可以设置任何控件之间的关系)->sizeClass(iOS8出现,用于解决越来越多的屏幕尺寸的适配问题)。
在iPhone3gs时代,手机的屏幕尺寸有且只有一种,也就是3.5英寸。开发app的时候,根本不用考虑同一个视图在不同尺寸的屏幕上显示的问题。iOS开发者完全可以用纯代码的方式把一个控件的frame写死。
后来apple公司推出了4.0英寸的iPhone5和iPhone5S,所以,针对于不同尺寸的屏幕,再把控件的frame写死就不可取了。(其实也不是不可取,很多iOS开发者做屏幕适配的时候不是用的autoresizing或autolayout,而是以代码的方式动态获取屏幕的尺寸,然后根据屏幕的尺寸来写死子控件的frame。使用这种方式你会在代码中无辜增加很多if...else... 的条件判断语句。另一种方式是获取到屏幕的尺寸后,按照控件和屏幕的比例来设置控件的frame,其本质上也是写死frame。所以这两种方式都不可取,毕竟将来会回出现越来越多的屏幕尺寸。从开发的角度,重复繁琐的代码会牵绊住开发者的进度;从程序设计角度,这样的设计思路不够高级,且日后不易于拓展和维护。)

三大适配技术

iOS屏幕适配主要有三个技术,分别是Autoresizing、AutoLayout、SizeClass。利用纯代码计算视图的frame我们在此就不多介绍。至于什么是Autoresizing?什么是AutoLayout?什么是SizeClass?我们用SToryBoard的一张截图可以说明问题,如下图。


sizeclass 和 autolayout.png


Autoresizing和Auto Layout不能共存,所以如果使用Autoresizing,就不能勾选Use Auto Layout。


autoresizing.png

(一)Autoresizing

Autoresizing是早期iOS设备机型很少、APP界面布局相对简单的背景下产生的一种屏幕适配技术。早期的iOS设备机型很少、屏幕尺寸单一、APP界面相对简单,屏幕适配并没有现在这么复杂,在当时这种背景下,产生了Autoresizing。当时这种情况下苹果推出Autoresizing也是可以理解的,但是如果放到现在这种大背景下,Autoresizing是不能够满足开发者的屏幕适配需求的,具体原因请见下文。

1.1.Autoresizing的启用

Xcode5之后,新建的项目默认使用AutoLayout。Autoresizing默认不启用,我们可以去掉use Auto Layout前面的对勾来启用Autoresizing,如下图。


启用Autoresizing.gif

1.2.Autoresizing介绍

有一种说法:autoresizing是为了解决iPad开发中横竖屏适配问题应运而生的。代码中的autoresizingMask和storyBoard中尺寸检查器中的Autoresizing是一回事。iPhone5开始,Xcode添加了autolayout功能。storyBoard默认采用autolayout,取代了之前的autoresizing。如果使用autoresizing,需要在以下位置去掉“Use Auto Layout”。

1.2.1.storyboard中使用Autoresizing


storyboard中使用Autoresizing.gif

从上图看出,storyBoard中的的Autoresizing只能设置两个父子视图之间的相对位置关系,一共6条虚线,分别是周围的四条虚线和方块内部的两条线。周围的四条虚线分别代表子控件距离父控件上、下、左、右之间的距离关系/或者叫约束关系,周围的四条虚线所包围的小方块代表子视图,小方块内部的两条带双向箭头的线分别代表子控件的宽度和高度。
当我们点击周围四条虚线时,虚线会变成实线,代表子控件和父控件在这个方向上的间距被固定了。当我们点击子视图内部的虚线时,同样也变为实线,代表子视图的宽度或者高度被固定了。
举个例子:当我们点击最左边的虚线时候,代表子视图距离父视图左边的间距被固定了,而其他三个方向的距离和宽高会随父视图的缩放二缩放。

1.2.2.代码中使用Autoresizing

我们不仅可以在storyboard中使用Autoresizing来约束父子视图,也可以使用代码来设置父子视图之间的位置关系。UIView有一个autoresizingMask属性,可以通过该属性来约束父子视图之前的位置关系,并且UIView还有一个BOOL类型的autoresizesSubviews属性,默认为YES,代表父控件会跟随子控件尺寸的变化而变化。和frame、bounds、center、transform等属性一样,autoresizingMask和autoresizesSubviews也是属于UIView的几何分类-UIViewGeometry中的属性。

@property(nonatomic) BOOL autoresizesSubviews; // default is YES. if set, subviews are adjusted according to their autoresizingMask if self.bounds changes
@property(nonatomic) UIViewAutoresizing autoresizingMask; // simple resize. default is UIViewAutoresizingNone

查看注释,autoresizesSubviews属性的大意是:默认autoresizesSubviews = YES。如果UIView设置了autoresizesSubviews,那么他的子控件的bounds如果发生了变化,他的子控件将会根据子控件自己的autoresizingMask属性的值来进行调整。
那么autoresizingMask又是什么呢?
autoresizingMask是一个枚举值,作用是自动调整子控件与父控件中间的margin(间距)或者子控件的宽高。默认其枚举值是UIViewAutoresizingNone。如下是其全部的枚举值:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { 
    UIViewAutoresizingNone = 0, 
    UIViewAutoresizingFlexibleLeftMargin = 1 << 0, 
    UIViewAutoresizingFlexibleWidth = 1 << 1, 
    UIViewAutoresizingFlexibleRightMargin = 1 << 2, 
    UIViewAutoresizingFlexibleTopMargin = 1 << 3, 
    UIViewAutoresizingFlexibleHeight = 1 << 4, 
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

从上面给出的代码可以看出,autoresizingMask有7个枚举值,除了UIViewAutoresizingNone之外还有6个枚举值,分别对应storyboard中的那6条虚线。所以,storyboard和代码是相同的,无论什么视图,凡是可以通过storyboard进行设置的属性,都有与之对应的属性代码,我们也可以使用代码的方式实现。毕竟,storyboard中的属性最终还是会翻译成可执行的代码,只不过XCode利用可视化的storyboard工具帮助我们完成了代码完成的事情。
值得注意的是:autoresizingMask的枚举值是使用位移的形式给出的,这样设置的好处在于,当我们使用代码给某个视图设置autoresizingMask属性时,我们可以给autoresizingMask属性指定多个枚举值。指定方式如下:

[subView setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin | 
![7.gif](http://upload-images.jianshu.io/upload_images/1055199-b48a0ccf7076c294.gif?imageMogr2/auto-orient/strip)
FlexibleLeftMargin];

甚至我们可以使用这种方式:

[subView setAutoresizingMask:5];

系统自动把5分解成5 = 4 + 1。然后我们就可以知道:4 = 1 << 2 = UIViewAutoresizingFlexibleRightMargin;1 = 1 << 0 = UIViewAutoresizingFlexibleLeftMargin。所以上面的代码就被解释成:

[subView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin];  // 子视图距离父视图左右间距不变,宽度随父视图宽度的缩放而缩放。

不难发现,这样以位移的方式指定autoresizingMask枚举值,也契合了storyboard中可以给子控件设置多个方向的约束的情景。如下图:


storyBoard上的autoresizing.gif

注意:Autoresizing只能设置父子视图之间的关系,也就是说,Autoresizing只能控制子视图和父视图之间的位置/大小关系。Autoresizing不能设置兄弟视图之间的关系,当然也不能设置完全不相关的两个视图之间的关系。正因为Autoresizing只能设置父子视图之间的关系,所以,Autoresizing只能应用于两个视图之间,不能应用于三个或者更多视图之间。毕竟,一个儿子不可能有两个亲爹。
注意:UIView的autoresizesSubviews属性为YES时(默认为YES),autoresizingMask才会生效。也就是说,当我们想要利用autoresizingMask指定某个控件和其父控件的关系时候,必须autoresizesSubviews = YES。
注意:值得注意的是,autoresizingMask的每个枚举值都带有Flexible这个单词,其意思是:灵活的,弹性的。所以,我们在对其枚举值进行一一翻译。

      UIViewAutoresizingNone // 就是不自动调整。
  UIViewAutoresizingFlexibleLeftMargin // 自动弹性的调整与superView左边的距离,保证与superView右边的距离不变。
  UIViewAutoresizingFlexibleRightMargin // 自动弹性的调整与superView的右边距离,保证与superView左边的距离不变。
  UIViewAutoresizingFlexibleTopMargin // 自动弹性d调整与superView顶部的距离,保证与superView底部的距离不变。
  UIViewAutoresizingFlexibleBottomMargin // 自动弹性的调整与superView底部的距离,也就是说,与superView顶部的距离不变。
  UIViewAutoresizingFlexibleWidth // 自动弹性的调整自己的宽度,保证与superView左边和右边的距离不变。
  UIViewAutoresizingFlexibleHeight // 自动弹性的调整自己的高度,保证与superView顶部和底部的距离不变。

看完翻译才恍然大悟,原来这些枚举值和storyboard中的虚线是相反的,当我们点击了storyboard中国的某个虚线后代表其间距被固定,而我们用代码设置则代表相反方向的间距被固定。
但是,例外的是,storyBoard中子控件内部的两个带箭头的虚线和枚举值UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight具有相同的意义。也就是说,当我们点击了storyBoard中子控件内带箭头的水平虚线使之变为实线时,就相当于[subView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];


[subView setAutoresizingMask-UIViewAutoresizingFlexibleWidth].gif

当我们点击了storyBoard中子控件内带箭头的垂直虚线使之变为实线时,就相当于[subView setAutoresizingMask UIViewAutoresizingFlexibleHeight];


[subView setAutoresizingMask UIViewAutoresizingFlexibleHeight].gif

注意:简而言之,4条margin虚线代表设置autoresizingMask中的与之对应的4枚举值,而实线的width和height才代表设置autoreMask中与之对应的2个枚举值。

1.2.3.Autoresizing的组合场景

autoresizingMask枚举值及其对应的storyBoard预览效果说明

UIViewAutoresizingMaskNone
view的frame不会随superview的改变而改变,相当于frame(右图的xib中预览效果与实际效果有差,实际效果是view的上边距不变)


UIViewAutoresizingMaskNone.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin
view和其superView左间距和上间距固定,宽高固定,右间距和底部间距随父控件的缩放而按比例缩放


UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin
view与其superView上间距固定,右间距固定,宽高固定,左间距、下间距锁父控件的缩放而缩放


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin
view与其superView的右间距、底部间距固定,宽高固定,上间距、左间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin
view与其superView的左间距、底部间距固定,宽高固定,右间距、上间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight

view与其superView的上间距、左间距、底部间距固定,宽度固定。高度、右边距随父控件缩放而缩放


UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth
view与其superView的左间距、上间距、右间距固定,高度固定。宽度、底部间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight
view与其superView的上间距、右间距、底部间距固定,宽度固定。高度、左间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth
view与其superView的左间距、右间距、底部间距固定,高度固定。宽度、上间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view与其superView的左间距、上间距、底部间距固定。宽度、高度、右间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view与其superView的左间距、上间距、右间距固定。宽度、高度、底部间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view与其superView的上间距、右间距、左间距固定。宽度、高度、左间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view与其superView的左间距、底部间距、右间距固定。宽度、高度、上间距随父控件的缩放而缩放


UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottom
view与其superView的宽高比例维持不变,上下左右间距也随其superView的缩放而缩放


UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottom.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin
view与其superView的左右间距固定,高度固定,宽度、上间距、底部间距随其父控件的缩放而缩放


UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
view与其superView的上下左右边距的比例维持不变,宽高固定,反映在storyBoard中,就是什么都不设置


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin
左边距、右边距、宽按比例调整,上边距固定,下边距固定,高度固定(右图的xib中预览效果与实际效果有差,实际效果是view的上边距不变)垂直方向是同样效果,故不列举


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin.gif

UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
自动调整view的宽和高,保证上下左右边距不变。如把tableView设置为此属性,那么无论viewController的view是多大,都能自动铺满


UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

不合理布局

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin
view与其superView的左边距和右边距的比例维持不变,上下间距固定,宽高固定(下图的xib中预览效果与实际效果有差,实际效果是view的上边距不变)这种约束方式相当于上下间距固定,宽高固定,那么父控件高度缩放的时候就会产生冲突,所以这种布局方式是不合理的


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin.gif

topAndBottom.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
view与其superView的上边距和下边距的比例维持不变,左右间距固定,宽高固定(这种约束方式相当于左右间距固定,宽高固定,那么父控件宽度缩放的时候就会产生冲突,所以这种布局方式也是不合理的)


UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin.gif

leftAndRight.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth
view与其superView的左边距和width按比例调整,高度固定,右边距固定,上边距固定,下边距固定(下图的xib中预览效果与实际效果有差,实际效果是view的上边距不变)(这种约束方式相当于上下间距固定,高度固定,那么父控件高度缩放的时候就会产生冲突,所以这种布局方式也是不合理的)


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth .gif

综上发现,只要是我们在水平方向同时固定了左边距和右边距,那么我们千万不能固定子控件的宽度(反应在storyBoard中的设置,也就是必须使控制子控件宽度的虚线变为实线)。同理, 如果垂直方向同时固定了上边距和下边距,那么我们不能固定子控件的高度(反应在storyBoard中的设置,也就是必须使控制子控件高度的虚线变为实线)。

控制器的view的autoresizing

注意:如果我们在storyBoard中选中控制器的view,然后在尺寸检查器中查看会发现,autoresizing中控制子控件的宽度和高度的虚线自动变成了实线(然而我并没有点击),这是因为控制器的view的宽高是一个默认值,默认和屏幕的尺寸相等,所以我们不能通过autoresizing来设置控制器的宽高。从另一个角度也能解释:autoresizing是约束子控件和父控件之间的位置关系的,控制器的view并没有父控件,所以不能通过autoresizing来约束控制器的view。

(二)AutoLayout

AutoLayout的前身是Autoresizing,也就是说,AutoLayout旨在替代Autoresizing。AutoLayout自iOS6开始引入,但由于当时XCode4当时对AutoLayout的支持不是很好,所以在XCode5/iOS7及其之后,AutoLayout才开始被广泛应用。
注意:既然前面已经说了,AutoLayout旨在替代Autoresizing,所以在同一个项目中,AutoLayout和Autoresizing是不能共存的,二者只能选其一,如果你选择了AutoLayout,那么Autoresizing自动被屏蔽掉;如果你选择了Autoresizing,那么AutoLayout自动被屏蔽掉。XCode5及其之后的版本,默认新建的项目就是使用AutoLayout,不过我们可以在项目中进行更改,如下图。


sizeclass和autolayout只能二选一.gif

Auto Layout is a Constraint-Based, Descriptive Layout System.翻译过来大意是:autolayout是一个基于约束的、描述性的布局系统。autolayout之所以能够进行屏幕适配,是因为他和autoresizing一样,都是对屏幕上的控件相对位置的设置,而不是绝对位置。用苹果官方的话,Auto Layout是一个基于约束的,描述性的布局系统。所谓基于约束就是代表我们可以为需要布局的子控件添加一些约束对象来限制他在屏幕上显示的位置。所谓描述性是指其约束的设置可读性较高,接近于人类语言。

1.约束
    每在Storyboard中对控件添加一个约束(autolayout的约束), 就代表添加一个约束对象。比如,给storyBoard中的某个子控件A设置了宽度和高度、距离父控件上下左右之间的间距,就相当于给这个控件添加了6个约束,也就产生了6个约束对象。
 2.约束错误(红色箭头)
    如果看到Storyboard中有红色的箭头, 代表约束有错误
    注意: 约束有错误, 不代表运行会错误, 约束有错误同样可以运行
    注意: 红色箭头是程序员必须解决的

 3.为什么会有约束错误?
    3.1缺少约束
    3.2约束冲突

 3.1缺少约束
 >autolayout的本质和frame差不多
 >如果通过frame来设置一个控件, 必须设置这个控件的x/y/width/height, 控件才能按照我们的需求显示
 >如果是通过autolayout来设置一个控件, 也必须设置这个控件的x/y/width/height, 控件才能按照我们的需求显示
 >也就是说, 如果说x/y/wedth/height只要有一个没有设置都会报错, 就是缺少约束
 >因为autolayout对控件的约束是一种相对位置的约束,所以我们可以通过间接的方式来设置约束。比如,给某个子控件A设置了左边距和右边距后,虽然没有明确指定子控件A的宽度,但是其左右边距一旦设置,那么宽度可以根据子控件A和父控件左右之间的边距自动推算出来。这就是我所说的间接的、相对位置的约束。
 3.2约束冲突
 >约束可以重复添加,但容易引发约束冲突
 >例如先约束某个子控件A的高度等于100,然后又给这个子控件A添加了一个高度约束, 约束高度等200, 那么这两个约束就产生了冲突,控件A不知道他自己的高度是100还是200,所以就会产生约束冲突,系统就会报错。


 红色:
 距离顶部有20 == 相当于设置了Y
 距离左边有20 == 相当于设置了x
 设置宽度等于100
 设置高度等于100

 4.约束警告
  如果看到Storyboard中有黄色的箭头, 就是警告
 > 警告代表着当前控件在storyBoard中呈现的位置或者尺寸和程序运行后实际呈现的效果不一样,导致约束警告的原因往往是没有更新控件的约束,但并不影响其真实效果,也不会报错。
*/

}

autolayout初步认识


对齐约束设置.png

约束设置.png

Top Layout Guide.png


注意:用storyBoard设置约束的时候,注意有一个Constrain to margins,默认打勾,也就是默认会给视图添加一个20的左右边距(上下不会添加),目的是让视图在iPhone6P和iPhone6SP上显示的更好看一些。 


Constrains to margins.gif

约束的添加规则

1.两个同层级view之间的约束关系,添加到他们共同的父view上


Snip20160515_1.png

2.两个不同层级上的view之间的约束关系,添加到他们最近的共同的父view上


Snip20160515_2.png

3.两个有层次关系的view之间的约束关系,添加到层次较高的view上


Snip20160515_3.png

4.如果view的约束只和自己有关系,那么添加到自己身上。比如宽高约束。

UILabel使用AutoLayout

UILabel默认内容的显示方式是垂直居中的。如果用autolayout给UILabel设置约束,只需要设置x、y、width,无需设置height,UILabel会自动包裹内容,并且随内容的多小而变化。如果我们通过约束给定了UILabel的width = 100,但是内容仍然少的可怜,不能包裹,可以把宽度设置为<=100,此时,label的宽高都能包裹住内容。高度的设置同理可证。
当然,我们也可以只给UILabel设置x、y。但必须要保证UILabel的text属性有内容,否则UILabel显示不出来(这是初学者经常犯的错误)。原因在于,UILabel是根据内容自动调整宽度和高度,如果没有内容,那么宽度和高度就是0,导致UILabel无法显示。

父控件随子控件变化而变化

如果希望父控件随子控件(UILabel/UIView)高度的变化而变化,就不要给父控件添加高度约束,只需要子控和向父控件在垂直方向上添加约束,这样子控件高度改变,父控件高度也会随之改变。

AutoLayout动画

事实上,我们在xib或者StoryBoard上给控件添加的约束,也是会被翻译成代码执行的。那么添加的那些约束会被翻译成什么呢?本质上,在xib或者StoryBoard上添加的也U树都是NSLayoutConstraint类型的对象。可以通过在StoryBoard上的控件和对应的.h或者.m文件之间拖线添加IBOutlet引用来证明。

#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
@property (weak, nonatomic) IBOutlet UIView *customView;
@end

@implementation ViewController

- (void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
//    WSLog(@"%f", self.heightConstraint.constant);
    self.heightConstraint.constant += 10;
    self.topConstraint.constant += 100;
    [UIView animateWithDuration:2.0 animations:^{
        [self.customView layoutIfNeeded];
    }];
}
@end

纯代码给控件添加约束

如下图,假设给控制器的view添加一个宽高均为100、水平、垂直居中的控件


Snip20160515_4.png


代码如下:

 // 1.创建控件
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
#warning 注意: 通过代码给子控件添加Autolayout约束,必须要先把子控件添加到父控件身上!

    // 2.创建约束
#warning 注意点: 如果通过代码来设置Autolayout约束, 那么必须先禁用Autoresizing
    redView.translatesAutoresizingMaskIntoConstraints = NO;

    // 2.1宽度
    NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0 constant:100.0];
    // 将约束添加给自己
    [redView addConstraint:width];

    // 2.2高度
    NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0 constant:100.0];
    // 将约束添加给自己
    [redView addConstraint:height];

    // 2.3水平居中
    NSLayoutConstraint *xCos = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
    // 将约束添加到层次较高的父view上
    [self.view addConstraint:xCos];

    // 2.4垂直居中
    NSLayoutConstraint *yCos = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0];
    // 将约束添加到层次较高的父view上
    [self.view addConstraint:yCos];

AutoLayout第三方开源框架-Masonry

Masonry是目前最流行的、最常用的AutoLayout的第三方开源框架。Masonry采用链式编程思想,极大的方便了开发者。大家可以在GitHub上找到Masonry。
同样的问题,还是给控制器的view添加一个宽高均为100、水平、垂直居中的控件的Masonry的代码,看起来就简洁多了,Masonry代码如下:

    // 1.创建一个控件
    UIView *redVeiw = [[UIView alloc] init];
    redVeiw.backgroundColor = [UIColor redColor];
    [self.view addSubview:redVeiw];

    // 2.禁止红色View的Autgoresizing
    redVeiw.translatesAutoresizingMaskIntoConstraints = NO;

    [redVeiw mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.equalTo(100);
        make.height.equalTo(100);
        make.centerX.equalTo(self.view.mas_centerX);
        make.centerY.equalTo(self.view.mas_centerY);
    }];

注意:一般情况下,多数使用storyBoard中的autolayout设置约束,只有在万不得已的情况下才用代码设置autolayout约束,这种情况一般是,被约束的控件是代码创建的,或者被约束的控件的父控件是代码创建的。
本篇文章没有讲解苹果自家的AutoLayout语言—VFL。因为笔者对VFL不使用不多,工作中几乎没有用到过,所以本篇文章就略过,感兴趣的读者可以自己研究。

(三)SizeClass

如下图,就是sizeClass:


Snip20160515_5.png

iOS8/XCode6才开始引入的。SizeClass依赖于AutoLayout,也就是说,如果你的项目中用到了SizeClass,那么必须要使用AutoLayout。所以,不存在SizeClass和Autoresizing联合使用的情况!
确切的说,sizeClass并不是一种屏幕布局技术,通过上面对autoresizing和autolayout的介绍,我们知道了autoresizing和autolayout是对屏幕上控件的相对位置进行的设置,也就是说,autoresizing和autolayout是针对于屏幕上的子控件而产生的技术。但sizeclass是对不同尺寸的屏幕的区分,sizeclass把不同尺寸(包括横屏和竖屏)的屏幕进行了分类,无论是iPhone还是iPad设备,其宽度和高度都被划分为三种类型:compact(紧凑)、regular(正常)、any(任意)我们只要针对于某一类型的屏幕进行布局,那么布局出来的界面可以显示在属于该类型的所有尺寸的屏幕上。

很多人认为sizeclass对屏幕宽度和高度的区分分为三种,compact(紧凑)、regular(正常)、any(任意)。这三种类型进行组合可以有9种结果。但在我看来,sizeclass对屏幕的区分只有两种:compact和regular。any并不是真正的类型,他只是代表了compact和regular。正像runLoop中的commonMode一样,并不是一种真正的mode,只是代表了defaultMode和trackMode。
sizeClass尺寸对照:


Snip20160515_6.png

一般情况下,StoryBoard中的sizeclass是(any,any)的。在sizeclass为(any,any)时布局的控件可以显示在任何尺寸的设备上,包括所有尺寸的iPhone和iPad。如果我们选择sizeclass为(compact,regular),那么在storyBoard上布局的控件只会显示在宽度为“紧凑”,高度为“正常"状态的设备上,也就是所有的“竖屏状态”的iPhone上。横屏状态的iPhone不会显示这个控件,横屏和竖屏状态的iPad也不会显示这个子控件。
所以,当我们希望某个控件在横屏是显示,在竖屏时不显示的时候,可以考虑有sizeClass这种技术。
并且,在iPad开发时,针对于同一界面,我们通常需要对横竖屏的iPad分别进行布局,此时也可以使用sizeClass,我们只需要切换storyBoard底部的sizeClass就可以布局初互不干扰的界面。

在出现sizeClass技术之前,我们用xcode新建的universal项目默认会有两个storyBoard,一个是专门为iPhone开发的storyBoard,另一个是专门为iPad开发的storyBoard。而在sizeClass出现之后,我们新建的universal项目就只有一个main.storyBoard。因为通过sizeClass我们可以在这一个main.storyBoard上为iPhone和iPad布局。

前面已经说过,我们选中sizeClass中一种屏幕类型,进行的布局只会出现在响应的设备上。比如,我选中w Compact H Regular。也就是宽度紧凑,高度正常。那么在这种状态的storyBoard上布局的控件只会出现在竖屏的iPhone设备上,不会出现在横屏的iPhone设备上。不信你继续往下看:

3.1.W Compact H Regular(宽度紧凑 高度正常)

1>选中storyBoard上的sizeClass为W Compact H Regular。下面会有提示:For all iPhone in portrait。也就是在这个状态下布局的控件只能出现在所有竖屏状态的iPhone上!


W Compact H Regular.png

2>此时会发现storyBoard上的初始化控制器由原来的正方形(W Any H Any)变为iPhone状态的长方形。
3>给storyBoard上的控制器添加一个水平、垂直居中、宽高都为150的红色button。如下图:


红色button水平垂直居中.png

4>然后我们预览在4英寸的iPhone设备上,横屏和竖屏的显示情况,如下图:


sizeClass-W Compact H Regular.gif

从上图,你会发现,当我把iPhone切换到横屏状态时,原本在竖屏显示的红色按钮不见了。原因就在于,这个红色按钮是在sizeClass为W Compact H Regular状态下添加给storyBoard上这个控制器的。这也验证了我前面说过的,sizeClass为宽度紧凑,高度正常状态时的布局智慧显示在所有竖屏的iPhone上。当然,此处,我只是拿4.0英寸iPhone举例,其他尺寸iPhone同理可证。在其他尺寸(3.5、4.7、5.5英寸)的横屏状态也不会显示。当然,在iPad全屏(横屏或竖屏)状态下同样不会显示。因为iPad 的屏幕尺寸根本就不在 W Compact H Regular这一列。
但是,在iPad分屏状态下是会显示的。

3.2.W Regular H Compact(宽度正常 高度紧凑)

1>还是上面的那个storyBoard,还是上面的那个带有红色按钮的控制器。我们现在把sizeClass切换为W Regular H Compact状态。如下图:


W Regular H Compact.png

2>你会发现,在设置sizeClass为W Regular H Compact时,下面的提示是:For 5.5-inch iphones in landscape。也就是说,在W Compact H Regular状态下给控制器的View添加的子控件只会出现在5.5英寸的横屏状态下的iPhone上。同时你也会发现,在W Compact H Regular状态下给控制器的view添加的红色button不见了。如下图:


横屏.png

3>然后我们给控制器的view左上角(此处的左上角是相对于垂直状态)添加一个绿色的button。如下图:


左上角添加绿色button.png

4>然后我们预览在5.5英寸的iPhone设备上,横屏和竖屏的显示情况,如下图:


sizeClass-W Regular H Compact.gif

从上图,你会发现,我们在W Regular H Compact状态下给控制器左上角添加的绿色的按钮只会显示在横屏状态下,切换到竖屏状态后,左上角什么都没有。这也验证了sizeClass为W Regular H Compact状态下的布局会出现在5.5英寸的横屏的iPhone上而不会出现在5.5英寸竖屏的iPhone上。
当然,切换到竖屏时,你同样发现了控制器中间出现了一个红色的按钮,没错,这就是我们在上一个例子中(W Compact H Regular状态)设置的那个水平、垂直居中的红色button。这也再次验证了,我们在不同的sizeClass下的布局并不会相互影响。
我们在W Regular H Compact状态下的布局不是说好了“只会”出现在5.5英寸的横屏状态下吗?上面只是验证了会出现在5.5英寸的横屏状态的iPhone上,但是并没有验证“只会”这个词语。请看下图:


sizeClass-W Regular H Compact2.gif

上图是以4.0英寸的iPhone设备为例进行的验证,你会发现,在4.0英寸的设备处于横屏状态时,左上角并没有出现绿色的button。至此,我们证明了sizeClass为W Regular H Compact时,在storyBoard上的控制器上的布局只会出现在5.5英寸的横屏状态的iPhone设备上。

3.3.W Regular H Regular(正常宽度 正常高度)

1>还是上面那个storyBoard,还是上面那个控制器,我们现在把sizeClass切换为W Regular H Regular,你会发现下面的提示:For iPads in portrait or landscape。也就是说,在sizeClass为W Regular H Regular状态下的布局只会出现在所有横屏或竖屏的iPad设备上,并不会出现在横屏或者竖屏的iPhone设备上。


W Regular H Regular.png

2>和上面那个例子一样,我们切换了sizeClass后,storyBoard上的控制器也变了形状--变成了和iPad一样方方正正的一个控制器。并且在左上角绿色的button不见了。"好像"变成了一个干干净净的控制器。


控制器.png

3>然后给控制器的右上角添加一个蓝色的button,如下图:


右上角添加蓝色button.png

4> 然后我们预览在iPad和iPhone设备上横竖屏的显示情况,如下图:

  • iPad设备横竖屏显示情况,如下图(因屏幕尺寸太小,需要滚屏,显示效果不好):

sizeClass-W Regular H Regular.gif
  • iPhone设备横竖屏显示情况,如下图:

sizeClass-W Regular H Regular in iPhone.gif

从上图,可以看出,在iPhone上无论是横屏还是竖屏,右上角都没有显示那个蓝色的button。综上,验证了sizeClass为W Regular H Regular状态时,在控制器上的布局只会显示在iPad横屏或者竖屏状态下,而不会显示在iPhone的横屏或者竖屏状态下。

总结

上面我列举了三种sizeClass状态下布局显示情况,而按照sizeClass的九宫格组合情况来看,sizeClass应该有9个不同的组合。当然any代表了compact和regular(正常和紧凑)。比如,当我们选择sizeClass为 W Regular H Any(宽度正常 高度任意)时,其实这代表了两个不同的sizeClass:W Regular H Regular (宽度正常 高度正常)和 W Regular H Compact(宽度正常 高度紧凑)。也就是说,在sizeClass为 W Regular H Any(宽度正常 高度任意)状态下的布局相当于在sizeClass为 W Regular H Regular(宽度正常 高度正常) 和W Regular H Compact(宽度正常 高度紧凑)布局之和。换句话说,在sizeClass为W Regular H Any(宽度正常 高度任意)下的布局的控件,不管高度如何,只要宽度正常就会显示出来。

未完待续...

文/VV木公子(简书作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载转载请联系作者获得授权,并注明出处,所有打赏均归本人所有!

如果您是iOS开发者,或者对本篇文章感兴趣,请关注本人,后续会更新更多相关文章!敬请期待!

iOS原创精华
Web note ad 1