关于自动布局(Autolayout)

在Xcode中,自动布局看似是一个很复杂的系统,在真正使用它之前,我也是这么认为的,不过事实并非如此。

 

我们知道,一款iOS应用,其主要UI组件是由一个个相对独立的可视单元构成,这些可视单元有的主要负责向用户输出有用的信息,有些则负责信息的输入(交互),交互的过程中往往还伴随有动画的效果,已达到整个信息传递的连贯性以及用户体验的细腻感。可视单元,在实际开发中主要是view、button等,那么这些可视单元的关系由两个基本的关系构成:兄弟关系和父子关系,整个视图单元就是一个树形结构:

自动布局(Autolayout)_开发

对于任何一个UI组件,确定了它的(相对于父view)位置、大小也就确定了它在整个UI视图中的展示效果。

 

Autolayout(以及iOS8中新增的sizeclass)是为了解决这些UI可视单元或者元素是怎样布局、排列的问题。在过去只有iPhone4的时候,我们可以在代码里将没一个可视单元的位置写死,这样是没问题的,但随着iPhone5、6的发布,屏幕尺寸有了越来越多中可能,未来不排除更多尺寸的iPhone发布出来,这就要求我们的APP的UI元素具有在屏幕尺寸不同的设备上具有一定动态的可调性,已实现较好的UI展示效果。从目前苹果提供的技术来看,有下、中、上三种实现方法:

 

下策是,代码中判断当前设备的尺寸,对UI元素进行手工的调整,其缺点是显而易见的:代码复杂、容易出错、且维护难度大、灵活性极差;

 

中策是,通过设置可视单元(UIView UIButton...)的autoresizing属性,预设当该view所在的环境(父view)发生变化时它的尺寸和位置应该如何调整,该方法可以在Xcode的interface builder中(storyboard 或者 xib)设置完成,但其只能针对父子关系进行有限的调整,比如左边距是否固定,尺寸是否可变等,而对于兄弟关系的调整则无法实现,对于UI比较固定的APP这种调节方式也算基本满足需求;

 

上策就是结合使用autolayout和sizeclass对UI可视单元的父子关系、兄弟关系进行全方位的调整,而且调节精度更高:不仅能确定一个view的位置尺寸的变化依据是什么,还能对这些依据加以不同的优先级,先满足什么条件,再满足什么条件,对于重要的位置尺寸可以优先保证,这样整个APP就具有极强的动态可调性,满足不同设备、不同应用场景下的需求。

 

在目前苹果手机苹果尺寸多达四种的情况下,显然新的APP必须要采用上策来解决视图组件的布局问题。

 

Autolayout的作用非常明确:帮我们确定在不同设备、不同(父view)环境下,同一个可视单元所应具有合适的位置和尺寸,因此,当一个UIView上所施加的约束能够唯一确定它的frame(x, y, width, height)的时候我们的自动布局的使用才是正确的。而新手通常犯的两类错误就是约束不足(约束太少)和约束冲突两种(约束太多)。如果你给出的约束只能够确定这个view的大小,或者位置或者位置中的某一个项(比如x)的时候,就会出现约束不足的情况,在xib或者storyboard中,会以***的警告出现在左侧提示框内;如果你给出的约束推导出了两个甚至多个互相矛盾的位置尺寸结果的时候,就产生了布局错误,在编译的时候直接就build不过。

 

二、关于iOS8新增的sizeclass属性

在iOS8中,新增了Size Classes特性,它是对当前所有iOS设备尺寸的一个抽象,也是该抽象了,想想现在多少种iOS尺寸的设备吧:iPhone4-5-6-6plus、iPad、iPad mini、iWatch,如何还是按照以前那针对种特定设备来编写不同的布局的话,一定是很糟糕的一件事情。

 

现在有了sizeclass,事情就好办多了:你不是设备多吗,那我们就只把屏幕的宽和高分别分成三种情况:(Compact, Regular, Any),也即紧凑、正常和任意。这样宽和高三三一整合,一共9中情况。如下图所示,针对每一种情况,如果需要的话,我们可以单独在storyboard或xib中设置UIView的自动布局约束,甚至某一个button是否显示都是能轻松实现。

 

自动布局(Autolayout)_button_02

关于size class的详细解析,参考苹果文档和wwdc2014视:点击打开链接 (What's New in Interface Builder)。

 

三、storyboard中autolayout和size class的无敌配合

对Xcode的interface builder比较熟悉的童鞋应该对UIButton的超强定制性映像深刻:通过选择button的不同状态(normal、height、disabled...),我们可以单独设置每一种状态,button的background p_w_picpath、p_w_picpath、text color等属性,见下图:

自动布局(Autolayout)_动画_03

 

而Xcode6中对自动布局的重大变更有异曲同工之妙:开发者可以根据实际需要,针对size class的九种组合中的某一种或几种分别进行自动布局的设置,这样,当APP运行于不同屏幕、不同旋转方向的时候,就可以根据当前环境的size class情况使用我们预先设置好的布局信息,从而达到APP UI的极大灵活性。

 

和设置UIButton的不同状态的不同属性类似,我们首先选择一种size class,然后针对该种size class进行自动布局。下面我们以一个简单的布局场景为例进行说明:

 

假设,我们想实现下面这个效果:横屏和竖屏头像和label都能正常的现实,且在“比较恰当”的位置:显然横屏的时候,高度处于压缩的状态,(height: compact),我们需要先对正常的布局之外,还要添加一种(wAny, hCompact)size class的布局:

自动布局(Autolayout)_iPhone5_04

 

首先,我们对默认的sizeclass进行布局,确定头像和label的位置和尺寸:

自动布局(Autolayout)_iPhone5_05

 

设置完(wAny hAny)之后,点击wAny hAny文字(上图底部),选择(wAny hCompact):注意点击后弹出一个九宫格浮框,拖动鼠标即可选择响应的size class,注意在右下角(红色方框表示),还可以选择是否install,如果取消勾选,则这个头像在当前size class下就不会被加载(自然也就不显示出来)。

自动布局(Autolayout)_iPhone5_06

 

在新的size class下我们开始添加新的布局,注意,这里并没有覆盖上一种size class我们定义好了得布局,知识针对当前的size class添加新的、独立的布局信息,狡兔三窟,Xcode6这下子一口气给了我们九个窟窿,爽!

自动布局(Autolayout)_iPhone4_07

 

从此爱上iOS Autolayout

时间 2014-09-06 19:13:01  segmentfault-博客

原文  http://blog.segmentfault.com/ilikewhite/1190000000646452

主题 iOS开发

这篇不是autolayout教程,只是autolayout动员文章和经验之谈,在本文第五节友情链接和推荐中,我将附上足够大家熟练使用autolayout的教程。这篇文章两个月前就想写下来,但因为一直工作较多,没有时间来完成。今天终于狠下心,丢下代码不写,来完成他吧!

一、别和我提Autolayout,我想死!!

从iOS6/xcode4开始,苹果开始提供了autolayout——一种对不同屏幕尺寸有更好兼容的自动布局机制,但我相信大多数人在刚接触autolayout时,一定和我一样,几乎快被其折磨致死!

autolayout因为布局思路与传统frame有所不同,国内关于autolayout的教程有过少,且autolayout在刚上手时灵活性不易掌控,导致大家更多选择了放弃。

二、为啥我要用autolayout?

随着3.5寸/4寸iPhone在市面同时使用越来越多,以及即将上市的iPhone6、iPhone6L,不同尺寸、不同分辨率的iOS设备将会越来越多,使用传统frame布局的工作量必将越来越大;加上苹果发出的信号,使用autolayout势在必行。

好了,我该来表扬表扬autolayout了,它到底能解决什么问题,给我们带来哪些好处?


1)你基本上可以不用考虑3.5寸和4寸以及即将上市的x.x寸屏幕不同分辨率的问题,你终于可以不用在viewDidLoad方法里判断不同分辨率下,不同控件应该放在哪里,或者针对不同分辨率写不同的storyboard和xib;

2)你可以抛弃那些根据不同文字来计算tableViewCell、UILabel高度的代码了,因为autolayout会帮你自动计算好;

3)如果你的布局在横屏竖屏下变化不是特别大,你不用再为横着竖着写两套代码或者写两个storyboard/xib了;


再看看苹果的态度,默认就是选择了使用autolayout。虽然我现在仍有时会骂autolayout,但我仍然会坚决地选择走上这条道路。

三、Autolayout之折腾二三事

刚刚表扬完autolayout,那我得为和我一样,愿意选择走上这条道的同志们提点醒了,你究竟要做好哪些折腾的准备。

3.1.布局思维的转变

传统布局思路中,一个view在哪里有多大,那就写清楚它的坐标位置和宽高就定了,平时用CGRect和CGPoint这两种模型就足够了,而且它一定非常听你的话,写的是多少,它绝对就是多少;但是autolayout的思路却变化了,或者说改进了,它囊括了传统frame布局思路,除了可以告诉view的坐标和宽高,它更提供了一种相对的概念,比如:

1)view相对于屏幕视图左边5点,右边10点,上面15点,下面20点,如果屏幕的长宽比例发生了改变(比如从3.5寸的320:480变成了4寸的320:568,或者从横屏切换到了竖屏),view仍然会随着屏幕的比例而拉伸改变,仍然保持离屏幕视图左边5点,右边10点,上面15点,下面20点;

2)view1和view2之间相距10点,当屏幕尺寸发生改变或者旋转时,他俩仍然可以通过改变自身的尺寸或位置改变来保证它们中间就是相距10点;

3)...

所以,使用autolayout的第一步是你需要考虑它相对于superView或者brotherView的上下左右的距离,改变自己布局的思维。

3.2.使用autolayout可能会经常得到自己不想看到的样子,而且你改变frame还没用

frame时代,是你写的多少位置点就是多少位置点,view不会被自动的拉伸或者改变位置,但是autolayout中的view却会根据屏幕长宽比或者其他view的改变而改变,你经常就会看到被自动布局成了不是你想的样子,这也是太多人被折磨的原因。只要你考虑的相对的位置不正确,它真的就可能会乱掉。

3.3.autolayout的VFL(Visual Format Language)语法初看起来真蛋疼

好吧,既然使用了autolayout,使用frame来改变位置不起作用了,那我也用代码来完成autolayout总行了吧。但是,让我选一段最普通的VFL代码给你看看:

NSString *vfl = @"V:|-5-[_view]-10-[_p_w_picpathView(20)]-10-[_backBtn]-5-|";

纳尼?!这是什么地干活?!我又要付出学习成本了啊!!!其实这段话就是说,在垂直方向从上到下,view离父视图5点,p_w_picpathView距离view 10点,同时p_w_picpathView是20点高,backBtn离p_w_picpathView底部10点,距离父视图底部5点。

3.4.手动Constraint书写,那个长长长啊~~

当然,你还可以一个一个的写布局约束Constraint,就和frame分别指定origin和size类似,但是却像这样:

[self.view addConstraint: [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];

而上文中的“view离父视图5点,p_w_picpathView距离view 10点,同时p_w_picpathView是20点高,backBtn离p_w_picpathView底部10点,距离父视图底部5点”每一个逗号短句都是像这样的一个constraint。

四、你是不是已经准备放弃了?NO!说好的爱上Autolayout呢!

写到这里,我忽然觉得我是在黑autolayout了,不,看我的题目,我是真的已经爱上使用autolayout了。就让我来说说应该怎么使用autolayout。

4.1.autolayout一般应用步骤和最适宜场景

当你的页面不会变更整体布局和设计,只有在不同屏幕尺寸、不同文字和内容下有适应性的变化,那这种情况使用autolayout就再适宜不过了。不会在像frame的时代,苦逼的要为不同屏幕尺寸计算各自的位置点坐标和大小了。通常使用xcode->Editor->Pin/Align菜单为视图添加约束即可。一般通过InterfaceBuilder确定控件位置,当存在需要自动被拉伸、适应或位移的控件时就要添加constraint;具体使用教程可参考  《开始iOS 7中自动布局教程1》

4.2.你的视图有比较简单的布局改变

当需要产生动画或动态添加视图时,autolayout就暴露了出我认为让人抓狂的元凶——优先级(Priority)和布局冲突。autolayout对于相同方位的约束,如都是描述离superview上边缘距离的约束,如果这两个约束的数值不同,但是优先级一样,则autolayout将报布局冲突,将会根据其计算丢弃某一条约束(这时可能就会丢弃你想要的约束,而恰恰保留了你不想看到的布局)。所以,当我们发生布局变化时,无法像frame的绝对定位,直接改变,并且只有唯一的位置信息。那么,我们该怎么处理这种布局冲突呢?那就是让描述相同但数值不同的这两个约束采用不同的优先级。autolayout默认将使用数值较大的优先级约束。

但是当我们新增了一个更高优先级约束改变了视图布局,在完成一些操作后,又想变回去怎么办?这时就必须删除更高优先级的约束。

所以,对于视图有动态变更时,我的通常做法是:为需要变更的控件新增默认constraint,但对于这个默认constraint先降低优先级,在发生变化时再新增一个更高优先级的constraint2,且代码中用一个Dictionary缓存该constraint2的对象,便于我随时删除或重新新增,让视图来回变化。

4.3.你的视图有较为复杂的动画效果或者较大的布局改变

虽然autolayout可以完成所有的布局问题,但它仍然在某些情况下是不方便的,就像4.2节描述的,每次改变你必须新增或删除一个不同优先级的constraint,单说构造constraint对象的工作就够呛了,还可能必须缓存该对象,便于之后清除。所以,当你需要非常频繁的变更控件布局,并且变更的位置是不确定的(例如通过手势拖动一个视图到屏幕任意位置),那么,我建议此视图不要使用autolayout,而使用frame的所写即所得的绝对定位方式更好,你只需要充分考虑各种屏幕适配,并为其计算适合的坐标点即可。同时,我还建议这种频繁变更的视图甚至不要InterfaceBuilder来绘制,最好直接代码书写,因为一旦你勾选了autolayout,那么storyboard中的所有视图都将autolayout。而当你需要变更视图布局时,则必须使用

view.translatesAutoresizingMaskIntoConstraints = NO;superview.translatesAutoresizingMaskIntoConstraints = NO;

来避免为你的视图新增默认autolayout约束。

五、友情链接和推荐

1.  《开始iOS 7中自动布局教程1》

这个教程看完基本上可以比较熟悉的使用autolayout,再结合本文的一些经验,应该能够解决大部分问题了。而该文的教程2没有中文翻译,只有英文原版:  《Beginning Auto Layout Tutorial in iOS 7: Part 2》

2.  《Autolayout及VFL经验分享》

这篇文章以较简单的描述囊括了VFL使用方法和常用的autolayout技巧。足够大家使用了。

3.  《AutoLayout(自动布局)入门》

这篇文章精简的囊括了autolayout的代码操作方

这篇不是autolayout教程,只是autolayout动员文章和经验之谈,在本文第五节友情链接和推荐中,我将附上足够大家熟练使用autolayout的教程。这篇文章两个月前就想写下来,但因为一直工作较多,没有时间来完成。今天终于狠下心,丢下代码不写,来完成他吧!

一、别和我提Autolayout,我想死!!

从iOS6/xcode4开始,苹果开始提供了autolayout——一种对不同屏幕尺寸有更好兼容的自动布局机制,但我相信大多数人在刚接触autolayout时,一定和我一样,几乎快被其折磨致死!

autolayout因为布局思路与传统frame有所不同,国内关于autolayout的教程有过少,且autolayout在刚上手时灵活性不易掌控,导致大家更多选择了放弃。

二、为啥我要用autolayout?

随着3.5寸/4寸iPhone在市面同时使用越来越多,以及即将上市的iPhone6、iPhone6L,不同尺寸、不同分辨率的iOS设备将会越来越多,使用传统frame布局的工作量必将越来越大;加上苹果发出的信号,使用autolayout势在必行。

好了,我该来表扬表扬autolayout了,它到底能解决什么问题,给我们带来哪些好处?


1)你基本上可以不用考虑3.5寸和4寸以及即将上市的x.x寸屏幕不同分辨率的问题,你终于可以不用在viewDidLoad方法里判断不同分辨率下,不同控件应该放在哪里,或者针对不同分辨率写不同的storyboard和xib;

2)你可以抛弃那些根据不同文字来计算tableViewCell、UILabel高度的代码了,因为autolayout会帮你自动计算好;

3)如果你的布局在横屏竖屏下变化不是特别大,你不用再为横着竖着写两套代码或者写两个storyboard/xib了;


再看看苹果的态度,默认就是选择了使用autolayout。虽然我现在仍有时会骂autolayout,但我仍然会坚决地选择走上这条道路。

三、Autolayout之折腾二三事

刚刚表扬完autolayout,那我得为和我一样,愿意选择走上这条道的同志们提点醒了,你究竟要做好哪些折腾的准备。

3.1.布局思维的转变

传统布局思路中,一个view在哪里有多大,那就写清楚它的坐标位置和宽高就定了,平时用CGRect和CGPoint这两种模型就足够了,而且它一定非常听你的话,写的是多少,它绝对就是多少;但是autolayout的思路却变化了,或者说改进了,它囊括了传统frame布局思路,除了可以告诉view的坐标和宽高,它更提供了一种相对的概念,比如:

1)view相对于屏幕视图左边5点,右边10点,上面15点,下面20点,如果屏幕的长宽比例发生了改变(比如从3.5寸的320:480变成了4寸的320:568,或者从横屏切换到了竖屏),view仍然会随着屏幕的比例而拉伸改变,仍然保持离屏幕视图左边5点,右边10点,上面15点,下面20点;

2)view1和view2之间相距10点,当屏幕尺寸发生改变或者旋转时,他俩仍然可以通过改变自身的尺寸或位置改变来保证它们中间就是相距10点;

3)...

所以,使用autolayout的第一步是你需要考虑它相对于superView或者brotherView的上下左右的距离,改变自己布局的思维。

3.2.使用autolayout可能会经常得到自己不想看到的样子,而且你改变frame还没用

frame时代,是你写的多少位置点就是多少位置点,view不会被自动的拉伸或者改变位置,但是autolayout中的view却会根据屏幕长宽比或者其他view的改变而改变,你经常就会看到被自动布局成了不是你想的样子,这也是太多人被折磨的原因。只要你考虑的相对的位置不正确,它真的就可能会乱掉。

3.3.autolayout的VFL(Visual Format Language)语法初看起来真蛋疼

好吧,既然使用了autolayout,使用frame来改变位置不起作用了,那我也用代码来完成autolayout总行了吧。但是,让我选一段最普通的VFL代码给你看看:

NSString *vfl = @"V:|-5-[_view]-10-[_p_w_picpathView(20)]-10-[_backBtn]-5-|";

纳尼?!这是什么地干活?!我又要付出学习成本了啊!!!其实这段话就是说,在垂直方向从上到下,view离父视图5点,p_w_picpathView距离view 10点,同时p_w_picpathView是20点高,backBtn离p_w_picpathView底部10点,距离父视图底部5点。

3.4.手动Constraint书写,那个长长长啊~~

当然,你还可以一个一个的写布局约束Constraint,就和frame分别指定origin和size类似,但是却像这样:

[self.view addConstraint: [NSLayoutConstraint constraintWithItem:blueView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:redView
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0]];

而上文中的“view离父视图5点,p_w_picpathView距离view 10点,同时p_w_picpathView是20点高,backBtn离p_w_picpathView底部10点,距离父视图底部5点”每一个逗号短句都是像这样的一个constraint。

四、你是不是已经准备放弃了?NO!说好的爱上Autolayout呢!

写到这里,我忽然觉得我是在黑autolayout了,不,看我的题目,我是真的已经爱上使用autolayout了。就让我来说说应该怎么使用autolayout。

4.1.autolayout一般应用步骤和最适宜场景

当你的页面不会变更整体布局和设计,只有在不同屏幕尺寸、不同文字和内容下有适应性的变化,那这种情况使用autolayout就再适宜不过了。不会在像frame的时代,苦逼的要为不同屏幕尺寸计算各自的位置点坐标和大小了。通常使用xcode->Editor->Pin/Align菜单为视图添加约束即可。一般通过InterfaceBuilder确定控件位置,当存在需要自动被拉伸、适应或位移的控件时就要添加constraint;具体使用教程可参考  《开始iOS 7中自动布局教程1》

4.2.你的视图有比较简单的布局改变

当需要产生动画或动态添加视图时,autolayout就暴露了出我认为让人抓狂的元凶——优先级(Priority)和布局冲突。autolayout对于相同方位的约束,如都是描述离superview上边缘距离的约束,如果这两个约束的数值不同,但是优先级一样,则autolayout将报布局冲突,将会根据其计算丢弃某一条约束(这时可能就会丢弃你想要的约束,而恰恰保留了你不想看到的布局)。所以,当我们发生布局变化时,无法像frame的绝对定位,直接改变,并且只有唯一的位置信息。那么,我们该怎么处理这种布局冲突呢?那就是让描述相同但数值不同的这两个约束采用不同的优先级。autolayout默认将使用数值较大的优先级约束。

但是当我们新增了一个更高优先级约束改变了视图布局,在完成一些操作后,又想变回去怎么办?这时就必须删除更高优先级的约束。

所以,对于视图有动态变更时,我的通常做法是:为需要变更的控件新增默认constraint,但对于这个默认constraint先降低优先级,在发生变化时再新增一个更高优先级的constraint2,且代码中用一个Dictionary缓存该constraint2的对象,便于我随时删除或重新新增,让视图来回变化。

4.3.你的视图有较为复杂的动画效果或者较大的布局改变

虽然autolayout可以完成所有的布局问题,但它仍然在某些情况下是不方便的,就像4.2节描述的,每次改变你必须新增或删除一个不同优先级的constraint,单说构造constraint对象的工作就够呛了,还可能必须缓存该对象,便于之后清除。所以,当你需要非常频繁的变更控件布局,并且变更的位置是不确定的(例如通过手势拖动一个视图到屏幕任意位置),那么,我建议此视图不要使用autolayout,而使用frame的所写即所得的绝对定位方式更好,你只需要充分考虑各种屏幕适配,并为其计算适合的坐标点即可。同时,我还建议这种频繁变更的视图甚至不要InterfaceBuilder来绘制,最好直接代码书写,因为一旦你勾选了autolayout,那么storyboard中的所有视图都将autolayout。而当你需要变更视图布局时,则必须使用

view.translatesAutoresizingMaskIntoConstraints = NO;superview.translatesAutoresizingMaskIntoConstraints = NO;

来避免为你的视图新增默认autolayout约束。

五、友情链接和推荐

1.  《开始iOS 7中自动布局教程1》

这个教程看完基本上可以比较熟悉的使用autolayout,再结合本文的一些经验,应该能够解决大部分问题了。而该文的教程2没有中文翻译,只有英文原版:  《Beginning Auto Layout Tutorial in iOS 7: Part 2》

2.  《Autolayout及VFL经验分享》

这篇文章以较简单的描述囊括了VFL使用方法和常用的autolayout技巧。足够大家使用了。

3.  《AutoLayout(自动布局)入门》

这篇文章精简的囊括了autolayout的代码操作方式。

如果在通读了本文和以上链接后还无法灵活运用autolayout,欢迎给我留言,大家一起讨论!同时欢迎关注我的博客  http://www.ilikewhite.com 和微博 http://weibo.com/ilikewhite