自动布局-Constraint

springs和struts的问题
你肯定很熟悉autosizing masks-也被认为是springs&struts模式。autosizing mask决定了当一个视图的父视图大小改变时,其自身需要做出什么改变。它有一个灵活的或固定不变的margins(struts)吗?它的宽和高要做出什么改变(springs)?
 
举个例子,一个宽度灵活的视图,如果其父视图变宽,那么它也会相应的变宽。一个视图右边拥有固定的margin,那么它的右边缘将会一直粘住其父视图的右边缘。
 
autosizing系统在简单的情况下非常奏效,但当你布局变得更复杂时,它立马跪了。让我们看一个springs和struts不能处理的示例。
 
打开Xcode5,创建一个基于Single View Application模板的iPhone项目。叫做"StrutsProblem":

点击Main.storyboard。在你做别的之前,首先将这个storyboard的自动布局关了。你需要在File inspector,第一个选项的第六个tabs里:

 
将Use Autolayout的box勾选去掉。现在storyboard使用旧的struts-and-springs模型。
 
注意:任何你使用Xcode4.5或更高版本中,nib或者storyboard文件都默认激活了自动布局。因为自动布局是iOS6以及以上系统的一个新特性,如果你想使用最新的Xcode开发兼容iOS5的程序,你需要将这个选项去掉。
 
拖拽三个新的视图到主视图上,并且像这样排列起来:

为了表述更清楚,这里给出每个视图的颜色,这样你就能分清哪个是驴子哪个是马了。
每个视图的inset到窗口的距离都是20点;视图之间的距离也是20点。底部的视图的宽是280点,上面两个视图的宽都是130点。所有的视图的高都是254。
 
 
在iPhone Retina 4-inch simulator上运行这个程序,并且将模拟器旋转到landscape。程序看起来便变成这副鬼样,这不是我想象的那样:

注意:你可以使用Hardware\Rotate Left和Rotate Right菜单选项旋转模拟器,或者通过按下键盘上的 ?   键,同时按下←或→。
 
而你想象的程序在landscape应该像这样:
 
很明显,三个视图的autosizing masks留下了一些需要改进的地方。将左上方视图的autosizing设置改成这样:
这将会让视图贴附左上边缘(不是右下边缘),并且当父视图大小改变时,重新调整自身水平和垂直方向的大小。
 
同样的,右上方视图的autosizing设置改成这样:


底部视图:

再次运行程序,并且旋转到landscape。现在看起来像这样:

已经很接近了,但又不完全一样。视图之间的padding不正确。换个说法就是视图的大小不完全正确。问题出在当父视图改变大小时,autosizing masks告诉子视图调整大小,但又没告诉子视图该调整多少(坑儿?)。
 
你可以调戏autosizing masks-比如,改变灵活宽度和高度设置(springs)-你不会得到完全正确的三个间距20点的视图。

为了解决这个springs和struts方法的布局问题,非常不幸,你需要额外写一些代码。
 
在旋转用户界面之前、之间、之后,UIKit会发送一些消息到你的视图控制器,你可以截获这些消息,从而对你UI做出改变。代表性的像viewWillLayoutSubviews,你会重写这个方法从而改变任何需要重新排列的视图的frame。
 
在这之前,你需要先做出一个outlet属性来引用这个视图。
 
切换到Assistant Editor模式,按住Ctrl,将三个视图都拖到ViewController.m中去:

 
分别链接视图到这三个属性:
  
  
  1. @property (weak, nonatomic) IBOutlet UIView *topLeftView; 
  2. @property (weak, nonatomic) IBOutlet UIView *topRightView; 
  3. @property (weak, nonatomic) IBOutlet UIView *bottomView; 
 
下面的代码写到ViewController.m:
   
   
  1. - (void)viewWillLayoutSubviews 
  2.     if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) 
  3.     { 
  4.         CGRect rect = self.topLeftView.frame; 
  5.         rect.size.width = 254; 
  6.         rect.size.height = 130; 
  7.         self.topLeftView.frame = rect; 
  8.   
  9.         rect = self.topRightView.frame; 
  10.         rect.origin.x = 294; 
  11.         rect.size.width = 254; 
  12.         rect.size.height = 130; 
  13.         self.topRightView.frame = rect; 
  14.   
  15.         rect = self.bottomView.frame; 
  16.         rect.origin.y = 170; 
  17.         rect.size.width = 528; 
  18.         rect.size.height = 130; 
  19.         self.bottomView.frame = rect; 
  20.     } 
  21.     else 
  22.     { 
  23.         CGRect rect = self.topLeftView.frame; 
  24.         rect.size.width = 130; 
  25.         rect.size.height = 254; 
  26.         self.topLeftView.frame = rect; 
  27.   
  28.         rect = self.topRightView.frame; 
  29.         rect.origin.x = 170; 
  30.         rect.size.width = 130; 
  31.         rect.size.height = 254; 
  32.         self.topRightView.frame = rect; 
  33.   
  34.         rect = self.bottomView.frame; 
  35.         rect.origin.y = 295; 
  36.         rect.size.width = 280; 
  37.         rect.size.height = 254; 
  38.         self.bottomView.frame = rect; 
  39.     } 
当视图控制器旋转到一个新的方向,这个回调将会被调用。它会监控视图控制器旋转的方向,并且适当的调整视图大小-在这种情况,根据已知iPhone屏幕大小会有一个hard-code(将可变变量用一个固定值来代替的方法叫做hard-code)偏移。这个回调会在一个动画block中发生,所以会动态的改变大小。
 
暂时还不要运行这个程序。首先你需要按下面的样子重新保存三个视图的autosizing masks,否则autosizing mechanism将会和你在viewWillLayoutSubviews中设置的位置和大小冲突。
这样就可以了,运行程序并且翻转到landscape。现在视图排列的非常号。翻转回到portrait,经核实,一切都良好。
 
这样奏效了,但是你需要为这个非常简单的例子编写大量的布局代码。想象一下,为布局付出的努力是非常复杂的,特别是个别视图动态的改变大小,或者子视图的个数是不固定的。
 
现在试着在3.5寸的模拟器上运行程序。我了个去。视图的位置和大小又错了,因为viewWillLayoutSubviews的hard-code坐标是基于4英寸大小的手机(320x568取代320x480)。你可以增加另一个if语句判断屏幕大小,并使用不同的坐标集,但是你可以看到这个方法很快变得不切实际。
注意:另一个你可以采取的方法就是为portrait和landscape模式建立独立的nibs。当设备旋转时,你从另一个nib中装载视图并替换掉当前的那个。但这任然需要做很多工作,并且维护两个nibs也会增加问题。当你使用storyboards替代nibs的时候,这个方法也变得不切实际。
 
自动布局拯救猿!
现在你将会看到如何用自动布局实现相同的效果,从ViewController.m中移除viewWillLayoutSubviews,因为我们不再需要写任何代码。
 
选择Main.storyboard,并在File inspector中选择开启Use Autolayout:
运行程序,旋转到landscape。现在看起来像这样:
让我们把自动布局付诸行动。当你点击顶部两个视图时,按住?键,这样两个视图都被选中了。从Xcode的Editor菜单中选择Pin\Widths Equally:
再次选中两个相同的视图,选择Editor\Pin\Horizontal Spacing。(尽管你执行完第一次Pin处理后,两个视图看起来还是被选中的,但其实他们只是在一个特别的布局关系显示模型里。所以你需要重新选择这两个视图)
 
storyboard现在看起来像这样:
橙色的"T-bar"形状代表视图间的约束。目前为止你增加了两个约束:一个等宽约束和一个位于两个视图间的水平约束。约束表达了视图之间的关系,并且他们是你使用自动布局建立布局最主要的工具。这货看起来有点吓人,但是一旦弄懂它的意思,便变得相当简单。
 
为了继续为这个屏幕简历布局,执行下面这些步骤。每个步骤增加更多橘黄色的T-bars.
 
o Top Space to Superview
o Leading Space to Superview
For the view on the right, choose:
o Top Space to Superview
o Trailing Space to Superview
And for the big view at the bottom:
o Leading Space to Superview
o Trailing Space to Superview
o Bottom Space to Superview
 
现在你应该有了下面这些约束:
注意T-bars仍然是橘黄色的。这意味这你的布局没有完成;自动布局没有足够的约束条件计算出视图的位置和大小。解决办法便是增加更多约束,直到他们变蓝。
 
按下 ?  键并选中三个视图。从Editor菜单中,选择Pin\Heights Equally。
 
现在选中左上角的视图和底部视图(像前面一样按住 键),选择Editor\Pin\Vertical Spacing.
 
Interface Builder看起来应该像这样:
T-bars已经变蓝了。自动布局现在已经有足够的信息来计算出一个有效的布局。这看起来有点杂乱无章,这是因为等宽和等高约束条件占去了很大空间。
 
运行程序并且...我说吧,不需要写一行代码便运行的很好了。不管你在哪个模拟器上运行;在3.5英寸和4英寸设备上,布局都运行良好。
这非常酷,但是究竟你在这做了什么?自动布局让你表达出布局中的视图和其他每个视图的关系,而不是需要你指出视图有多大,放在哪儿。你需要放置以下这些关系(即我们所谓的约束)到布局中:
1.左上角和右上角的视图总是有相等的宽度(也就是pin中第一个widths equally命令)。
2.左上角和右上角的视图水平方向有20点距离(也就是pin中的horizontal spacing)。
3.所有的视图总是有相同的高度(也就是pin中heights equally命令)。
4.上面两个视图和下面那个视图垂直方向上有20点距离(也就是pin中的vertical spacing)。
5.视图和屏幕边缘有20点空间(top,bottom,leading和trailing space相对于父视图的约束)。
 
这些就足以表达出自动布局该怎么放置视图,以及当屏幕大小改变时该如何处理。
你可以在左边Document Outline中看到你所有的约束,组名叫做Constraints(当你为storyboard激活自动布局时才会加进来)。
 
如果你在Document Outline中点击一个约束,Interface Builder将会在视图中高亮出它:
 
约束是一个真实的对象(NSLayoutConstraint),并且他们也有属性。比如,选择上面两个视图的间距约束条件(叫做"Horizontal Space(20)"),然后切换到Attributes inspector。你可以在那里通过编辑Constant字段改变边缘空间的大小。
将它设置为100,然后再次运行程序。现在他们边缘空间变得更宽了:
自动布局在描述视图上比springs和struts显得更有表现力。在这篇教程的剩余部分,你将会学到约束的一切,以及如何将他们应用到Interface Builder上来构造出不同种类的布局。
 
自动布局如何工作
正如你在上面测试样例中所看到的一样,自动布局最基本的工具是约束。一个约束描述了两个视图间的几何关系。比如,你可能有这样一个约束:
"label A右边缘和button B左边缘有20点的空白空间。"
 
自动布局会考虑到所有的约束,然后为你的视图计算出理想的位置和大小。你再也不需要亲自为你的视图设置frames了-自动布局会完全基于你为这些视图设置的约束为你做这个工作。
 
自动布局以前,你一直需要为视图的frames设置hard-code,要么在Interface Builder中将他们放置在特定的坐标,或通过传递一个rectangle到initWithFrame:,或者设置视图的frame,bounds或者center属性。
 
就你刚刚做的那个程序,你需要明确设置frames为:
还需要为这些视图设置自动调整大小的masks:
 
这再也不是你需要为屏幕设计所考虑的东西了。使用自动布局,你需要做这些:
视图的大小和位置再也不重要了,只有约束要紧。当然,当你拖一个新建的button或label到画布上时,它会有一定的大小,并且你会将它拖到某一位置,但这是只一个用来告诉Interface Builder如何放置约束的设计工具。
 
 
想你所想,如你所愿
使用约束最大的优势就是你再也不需要把时间浪费在坐标上了。相反,你可以向自动布局描述视图如何和其他视图相关联,自动布局将会为你完成所有困难的工作。这叫做根据目的设计(designing by intent)。
 
当你根据目的设计时,你表达的是你想要实现什么,而不需要关心它如何实现。"button的左上角坐标为(20,230)",现在你可以这么说了:button是垂直居中于它的父视图,并且相对于父视图的左边缘有一个固定的距离。
 
使用这个描述,不管父视图多大或多小,自动布局都可以自动计算出你的button需要在哪儿出现,
 
其他根据目的设计的示例(自动布局可以处理所有这些指令):
"这两个text fields的大小需要一直相等。"
"这两个button需要一直一起移动。"
"这四个labels需要一直右对齐。"
 
这使得你用户界面的设置更具描述性。你只需简单的定义约束,系统会为你自动计算frames。
 
在第一部分你看到,即使为几个视图在横竖方向上正确的布局都需要做大量的工作。有了自动布局,你可以绕过这些麻烦。如果你正确的设置了约束,那么在横竖屏方向上,布局将不需要做任何改变。
 
使用自动布局另一个重要的好处就是本地化。比如德语中的文本,出了名的比老奶奶的裹脚布还要长,适配起来是一件很麻烦的事。再次,自动布局拯救了猿,因为它能根据label需要显示的内容自动改变label的大小。
 
现在增加德语,法语或者其他任何一种语言,都只是设置约束的事,然后翻译文本,然后。。。就没有然后了!
获得自动布局窍门最好的方法就是使用它,所以这正是剩下教程中你会学到的东西。
 
注意:自动布局不仅对旋转有作用;它还能轻易的缩放你UI的大小从而适应不同尺寸的屏幕。这并不是巧合,当iPhone5拥有更高屏幕的同时,这个技术也同时加到了iOS中!自动布局能轻易的拉伸你程序的用户界面,从而充满iPhone5垂直方向上多出来的空间。随着iOS7中的动态类型,自动布局变得更加重要了。用户现在可以改变全局字体大小设置--有了自动布局,这将变得非常简单。
 
 
拥抱约束(courting constraints)
关闭你当前的项目并用Single View Application模板创建一个新的iPhone项目。叫做"Constraints"。任何用Xcode5创建出来的项目都会自动假定你会使用自动布局,所以你并不需要额外做任何事情。
 
点击Main.storyboard打开Interface Builder。拖一个新的Button到画布上。注意当你拖拽的时候,蓝色虚线将会出现。这写线用来做向导。
在屏幕边缘以及中心的时候,都会有向导线:
 
如果之前你已经使用过Interface Builder,那么你肯定看到过这些向导线。这对我们对齐控件有很大的帮助。
 
在Xcode4中激活自动布局时,向导线有另外一个目的。你任然可以用他们来对齐,但是他们也会告诉你新的约束将会在哪儿。如果你将button沿着向导线反方向拖拽到左上角时,Xcode4中的storyboard看起来便像这样:
有两个蓝色的东西附属在button上面。这些T-bar形状的对象便是约束了。Xcode 4的Interface Builder中不管你将UI控制器放在哪儿,它总是会给出有效的约束。理论上这听起来是个好主意,但是实践起来,在Interface Builder中使用自动布局却非常困难。
 
幸运的是,Xcode5中已经有所好转。将button拖拽到画布上之后并看不到T-bars形状的东西:
 
同时在Document Outline面板中也没用Constraints部分。得到结论:此时button上并没有设置任何约束。
 
那这是如何运作的呢?我们之前了解的自动布局总是需要足够多的约束才能决定视图的大小和位置,但是现在我们这儿跟本没有约束。确定这是一个完整的布局?
 
这这是Xcode5相对Xcode4来说最大的一个提升:再也不强制你总是有一个有效的布局。
 
注意:1.运行一个无效布局的程序是不明智的,因为自动布局不能正确的计算需要将视图放在哪儿。要么视图的位置是不可预知的(约束不够),要么程序将会崩溃(约束过多)。
 
2.Xcode4设法保证总是有足够多正确的约束来创建一个有效的布局。不幸的是,它经常会将你的约束替换为你并不想要的。这会令人很沮丧,正是因为这个原因很多开发者放弃了自动布局。
 
3.Xcode5中,当你编辑Storyboard时它允许你有不完整的布局,但它也会指出哪些地方你还需要修改。使用Interface Builder创建的自动布局驱动用户界面变得更有趣了,使用Xcode5也消耗更少的时间。
 
如果你根本不提供任何约束,Xcode自动分配一套默认的约束,正是我们所知的自动约束。它会在程序built的编译时间中去完成这些事,而不是设计时间。当你设计你的用户界面时,Xcode5中的自动布局为了不参与你的设计方法而努力工作,这这是我们喜欢它的原因。
 
自动约束为你的视图提供一个固定尺寸和位置。换句话说,视图总是拥有跟你在storyboard中看到的一样的坐标。这是非常方便的,因为这就意味着你可以大量的忽视自动布局。你可以为那些拥有充分约束的控件不增加约束,只为那些需要特殊规则的视图创建约束。
 
OK,让我们玩一玩约束并看看他们能做什么。现在,按钮是在左上角,并且没有约束。确认按钮跟两个拐角向导线对齐。
 
使用Editor\Pin菜单为按钮增加两个新的约束,看起来像这样:
这是Leading Space to Superview和Top Space to Superview选项。
 
所有的约束都会在Document Outline面板中列出来:
目前有两个约束,一个是button和main view左边缘的Horizontal Space约束,一个是button和main view上边缘的Vertical Space约束。这个关系通过约束描述起来便是:"button总是位于其父视图左上角20点处。"
 
注意:这些其实都不是非常有用的约束,因为他们有相同的自动约束。如果你总是想你的button相对于父视图左上角,那么你还不如不提供任何约束,让Xcode为你做这些。
 
现在拖动button并将它放到屏幕的右上角,再次和蓝色向导线对齐:
哇哦,这里发生了什么?在Xcode4中这会破坏旧的约束并赋值一个基于蓝色向导线的新约束,但是在Xcode5中button保留了现存的约束。但问题是button在Interface Builder中的大小和位置再也不和自动布局希望基于约束的大小和位置相符合了。这叫做错位的视图。(misplaced view)
 
运行程序。Button仍然会出现在屏幕的左上角:
 
当谈到自动布局,橙色代表坏的。Interface Builder绘制两个橙色方块:一个是虚线边框,一个是实线边框。虚线方块是根据自动布局显示视图的frame。实线方块是根据你在屏幕上放置的视图的frame。这两个应该吻合的,但是这里并没有。
 
如何修改取决于你想要达到什么目的:
1.你想让button附属于屏幕左边缘254点处吗?在这种情况下你需要将现存的Horizontal Space约束变大234点。这正是橙色badge中"+234"的意思。
2.你想让button附属于屏幕的右边缘?那么你需要移除现有的约束并重新创建一个新的。
 
删除Horizontal Space约束。首先在画布或Document Outline中选中,然后按键盘上的Delete键。
注意这次Vertical Space约束变橙色了。直到现在它都是蓝色的。那一个约束并没有任何错误;它的意思是剩下的没有足够的约束决定button完整的位置。你任然需要在X轴方向增加一个约束。
 
Note:你可能会奇怪,为什么Xcode不为X轴方向自动增加一个约束。Xcode中的规则是:Xcode只为那些你没有设置任何约束的对象创建自动约束。一旦你增加一个约束,你便是告诉Xcode你接管了这个视图。Xcode将不再增加任何自动约束,并希望你为这个视图增加需要的约束。
 
选中button,并选择Editor\Pin|Trailing Space to Superview.这迫使在button右边缘和屏幕右边缘增加一个新的约束。关系表达如下:"button总是位于距离其父视图右上角20点处。"
 
运行程序并旋转到landscape。注意button如何与屏幕右边缘保持相同距离:
 
当你放置一个对立于向导线的button(或者任何其他视图)并新建一个约束时,你会得到一个根据"HIG"(Apple's iOS Human Interface Guidelines document)定义的标准大小的间隔约束。对于边框来说,标准大小空间是20点。
 
现在将button向左拖拽一点:
由于视图错位,你得到了一个橙色虚线边框。我们假设这个button新位置的确是你想要的。创建完一个约束后做一些细微的调整是很常见的,但这却会导致橙色方块出现。一个修改方法就是移除约束并创建一个新的,但还有一个更简单的解决方案。
 
Editor菜单上有一个Resolve Auto Layout Issues子菜单。从这个菜单中,选中Update Constraints。就我这个情况来说,这会告诉Interface Builder需要将约束变大64点,像这样:
 
很好,T-bars又变蓝了,布局是有效的。在Document Outline中,你可以看到Horizontal Space约束不再有一个标准的间隔了:
到目前为止你已经尝试过了Horizontal Space和Vertical Space约束。还有一个"center"约束。拖拽一个新的Button对象到画布底部中心,根据向导线完好入位:
 
为了保持button在水平方向上一直居中对齐于父视图,你需要增加一个Center X Alignment约束。从Editor菜单选择Align\Horizontal Center in Container.这会增加一个很长的橙色线段:
 
 
线之所以是橙色是因为你才仅仅指定了button的X轴,但Y轴并没有指定约束。使用Editor\Pin菜单在button和视图底部间增加一个Vertical Space约束。看起来像这样:
 
如果你不知道原因,这是Bottom Space to Superview选项。Vertical Space约束使button远离视图底部(再一次使用标准间隔)。
 
运行程序并旋转到横屏模式。甚至在横屏模式,button也保持在屏幕底部的中心:
这就是你表达的意思---这个button始终应该位于底部中心。注意,你根本不需要告诉Interface Builder按钮的坐标是什么,除非你想将它固定在视图上。
 
 
通过自动布局,你再也不需要担心视图位置的精确坐标或视图大小了。相反,自动布局会根据你设置的约束得到这两个参数。
 
你可以在button的Size inspector中看到这个经典转移,现在有了很大的不同:
 
如果不使用自动布局,输入值到X,Y,Width或Height字段将会改变选中视图的位置和大小。使用自动布局后,你仍然可以输入新值到这些字段,但是如果你已经为视图设置了约束,那这可能造成视图错位。你将不得不更新约束来匹配新值。
 
举个例子,把button的宽度改为100,画布会变成这样:
 
Xcode4用Horizontal Space取代Center X Alignment约束,并且button上会产生一个新约束强制它的宽度为100 points。然而,Xcode5说,"如果你想让button宽度变为100 points,对我来说无所谓,但是你要知道约束并不是这么说的。"
 
在这种情况下你希望button是100点宽。对此有一个特殊的约束类型:Fixed Width约束。首先按一下Undo,这样button又居中了,T-bars也变蓝了。选中button并选择Editor\Pin\Width。这会在button下面放置一个新T-bar:
 
选中那个T-bar并在Attributes inspector中改变Constant为100.不管button的title多大或多小,这都会强制button的宽总是100点。为了能更好的看清你可以给button设置一个背景色:
 
 
你也可以在左边的Document Outline中看到这个新的Width约束:
 
与其他约束不同,在button和它的父视图之间,Width约束只会应用到button本身。你可以将这个认为是一个button本身和本身之间的约束。
 
你可能怀疑为什么button之前没有Width约束。自动布局是为何知道button有多宽?
 
事情是这样的:button自己是知道自己有多宽。它根据自己的title text加上一些padding就行了。如果你为button设置一个背景图片,它也会考虑进去。
 
这正是我们熟悉的intrinsic content size。并不是所有的控制器都有这个,但大部分是(UILabel是一个例子)。如果一个视图可以计算自己理想的大小,那么你就不需要为它特别指定Width或Height约束了,你将会在稍后看到更多相关内容。
 
为了恢复button到最佳大小,首先我们需要移除Width约束。然后选中button,并从Editor菜单中选择Size to Fit Content。这样就能够恢复button的固有的内容尺寸了。
 
孤掌难鸣
向导线不但出现在一个视图和它的父视图之间,而且也会出现在相同层级的视图之间。拖拽一个新的button到画布上进行演示。如果你将这个button拖近其他对象,这时他们的向导线将会开始相互影响。
 
将新button放到之前一个button的后面完好入位:

 
这还有一些向导虚线。Interface Builder识别出这两个button可以通过不同方式对齐—顶部,中心以及基线。
 
Xcode4会将这些显著的向导线转变成新的约束。但是在Xcode5中,如果你想让这两个button间有约束,你需要自己创建。之前你已经使用过Editor\Pin菜单来创建这两个视图间的约束,但是还有一个更简单的方式。
 
选中新的button并按住Ctrl拖拽到另一个button上,像这样:

放开鼠标按键,出现一个弹出框。选择第一个选项,Horizontal Spacing。

这将会创建一个新的约束:
 

 
它是橙色的,这意味着这个button至少还需要另一个约束。button的大小是知道的(使用intrinsic content size),并且还有一个button在X轴上的约束。只剩下Y轴没有约束了。
 
这种缺失约束的情况是很容易确定的,但是更复杂的设计可能就没这么明显了。幸运的是,你不再需要敏思苦想,Xcode已经记录并可以确切的告诉你缺少了什么。
 
在Document Outline中会有一个红色的小箭头,就在View Controller Scene后面。点击这个箭头便会看到所有Auto Layout问题:

 
我们将Y轴方向缺失的约束加进去。按住Ctrl并向下拖拽新的button:

这次弹出菜单有不同的选项了。这次菜单的选项是基于上下文环境的—你在哪些视图间拖拽以及鼠标移动的方向。选择Bottom Space to Bottom Layout。
 
现在新button有一个位于屏幕底部的Vertical Space,也有一个跟其他button相关联的Horizontal Space。虽然空间非常小(只有8 points),T-bar可能不大容易看到,但它就在那里。
 
点击Document Outline里面的Horizontal Space(8):

 
当你选中一个约束,它会高亮自己所属的控制器。这个特别的约束位于两个button之间。这个约束表达了:“不管第一个button在哪儿或多大,第二个button总是出现在第一个button的左边”。
 
选中黄色背景的button并输入较长的label,比如:“A longer label”。输入完成后,button会为新的text改变大小,并且另一个button会移开。
 
最终,它依附在第一个button的左边缘,这正是我们所期望的:

 
为了更好的摸索这是如何工作的,多练一些吧。拖拽另一个button到画布上并放到黄色button的上方,他们会垂直方向对齐到位(不要试着让两个button的左边缘对齐):

 
为新button设置一个绿色背景色,这样就可以更容易看出它的范围。
 
因为你将两个button对齐在一起,现在他们之间存在HIG推荐的8 points间隔。按住Ctrl在两个button之间拖拽将这变为一个约束。从弹出菜单中选中Vertical Spacing。
 
Note:“HIG”是“iOS Human Interface Guidelines”的简称,包含Apple推荐的良好的用户界面设计。任何iOS开发者都有必要读一读这个规范。HIG解释了哪些UI元素适合在什么情况下使用,以及最佳使用方式。你可以在这里找到。( https://developer.apple.com/library/ios/DOCUMENTATION/UserExperience/Conceptual/MobileHIG/Introduction/Introduction.html
 
然而你并没有被限制在controls间的标准间隔。约束是成熟的对象,就像视图一样,因此你可以改变它们的属性。
 
选中两个button之间的Vertical Space约束。你可以在画布上点击T-bar,虽然这有点麻烦。目前最简单的办法就是在Document Outline里面选择约束。一旦你选中约束,再切换到Attributes inspector:

 
在Constant字段里输入40改变约束大小。现在两个button更进一步的分开了,但是他们任然是连接在一起的:

 
运行程序并翻转到landscape模式查看效果:

 
button必然会保持他们垂直方向的排列,但是水平方向就不了!原因很明显:绿色button还没有X轴方向的约束。
 
为绿色button增加一个到屏幕左边缘的Horizontal Space并不能解决问题。这样的约束只会让绿色按钮总是保持在同一个X轴坐标,即便是在横屏模式下。这看起来感觉不大对,所以你需要表述这样一个目的:
“黄色button会一直水平居中,蓝色button左边缘会一直跟黄色button左边缘对齐。”
 
你已经为第一种情况创建了一个约束,但是第二个并没有。Interface Builder为对齐显示了向导线,这样你就可以将上面button一直拖拽到跟黄色左边缘对齐的位置:

 
如果你也在垂直方向上拖拽button,这时button框架和Vertical Space约束之间就不能达到正确的距离了。你在T-bar上将会看到橙色的badge:

如果发生这样的情况,简单的使用方向键将button微调到位,直到badge消失。
 
最终,按住Ctrl在两个button间拖拽,从弹出菜单中选择Left。这会创建一个约束:“两个视图的左边缘一直对齐”。换句话说,这两个button一直会有相同的X轴坐标。这时T-bars变成蓝色了:

 
运行程序并旋转到横屏模式:

 
何去何从?
现在你已经对自动布局进行了第一次尝试,感觉怎么样?这可能需要一些时间习惯,但是它能让你的工作更加简单,也会让你的app更加灵活。
 
想要学习更多的内容?继续阅读 第二部分吧,你将会继续在Interface Builder中使用button进一步理解Auto Layout提供的多种可能性,以及你可能遇到的问题。
 
最重要的是,你将会使用Auto Layout在一个真实的程序中创建一个逼真的布局。


(翻译)开始iOS 7中自动布局教程(二)

0
来源:cnblog    阅读:189   时间:2014-09-20

正文如下:


开始iOS 7中自动布局教程(一) 你已经看到旧的“struts-and-springs”模型让user interfaces不能较容易的解决所有的布局问题。自动布局是一个解决方案,但是也是因为它的强大,所以在使用它的时候,我们需要一点小技巧。

值得高兴的是,Xcode5让自动布局更容易了。如果你在Xcode4中尝试过自动布局并且放弃了,那现在我们希望你能再给它一个机会。我们将在Xcode5中使用它。

在第二部分和最后一部分自动布局的系列教程中,你将要继续学习所有关于约束的知识以及如何应用它们。

 

千里之行始于足下

这个自动布局的教程将从下面这个非常简单的app开始:

app中有两个设置了背景色的按钮,你可以更清楚的看到它们的边界。它们之间有一些约束。如果你学习的上一部分,你可以继续使用你之前的app,只要你移除界面上的其它两个按钮。

如果你打算重新开始,请使用Single View Application模板创建一个新的iPhone应用。拖两个按钮到场景中并且给它们设置背景颜色。使用Editor\Pin菜单在两个按钮之间创建一个Vertical Spacing约束,然后靠下的按钮创建一个Bottom Space to Superview约束(大小为20点)。使用Editor\Align菜单给黄色按钮创建一个横向居中的约束,然后两个按钮align the left edges(对齐左边缘)。

在Interface Builder中执行,一切看起来都很棒。但是让我们看看它们是怎么工作的。在ViewController.m中加入下面的方法:


- (IBAction)buttonTapped:(UIButton *)sender
{
    if ([[sender titleForState:UIControlStateNormal] isEqualToString:@"X"]) {
        [sender setTitle:@"A very long title for this button" 
                forState:UIControlStateNormal];
   } else {
        [sender setTitle:@"X" forState:UIControlStateNormal];
   }
}

给长标题和短标题按钮都设置单击触发事件。在Interface Builder中将按钮都连接到action方法上。按住ctrl拖拽每个按钮到view controller并且在弹出窗中选择buttonTapped。

运行这个app并且单击按钮,看看它是怎么变化的。可以在横屏和竖屏都进行测试。


不管按钮是长标题还是短标题,布局总是被安全的约束着:

  • 靠下的按钮总是在视图中横向居中
  • 靠下的按钮总是距离视图底部20点
  • 靠上的按钮总是和靠下的按钮左对齐,并且它们之间的距离总是40点。

上面三点就是你的user interface中所显示的全部说明

让我们来试试,移除左对齐约束(在outline窗口中选择并且按下Delete键),然后选中在Interface Builder里的所有按钮,在对齐菜单中选择对齐右边缘。现在再次运行你的app观察与之前的区别。

我们再来一次,这次选择Align\Horizontal Centers。它将使两个按钮总是居中对齐。运行app,点击按钮,看看它们是怎么运行的。(记住,如果你改变约束时看到了橘黄色的虚线矩形,你可以使用 Editor\Resolve Auto Layout Issuesmenu来更新按钮的大小和位置。)

处理宽度

Pin菜单有一个选项是Widths Equally。如果你在两个view中设置了这个约束,自动布局将总是让所有view的宽度等同于最大的那个view的宽度,让我们来试试。

选中所有按钮,并且选择Editor\Pin\Widths Equally。这就给所有按钮都加上了一个新约束:


在这之前,你可以先看看教程第一部分的关于约束类型的内容。它看起来就像一个T字架,但在中间有一个写着“=”号的圆。

当然,也许是两个T字架,在Outline文档中显示了一个单独的Equal Widths约束:


改变按钮上的label文本,两个按钮的尺寸都会同时改变。将下面的按钮的label文本改为“X”。这时候,你会发现上面的按钮,不能再匹配它的文本了:


那自动布局是怎么知道使用哪个按钮的宽度呢?如果你留心一点,你可能注意到上面的按钮的尺寸不再正确了:


显然这不是你想要的效果,选择上面的按钮并选择Size to Fit Content从Editor菜单中(或使用快捷键 ⌘ =)。现在,文本再次匹配按钮了,并且因为Equal Widths约束的原因,黄色按钮也改变了尺寸。

运行app并且单击按钮,这个按钮现在总是一样宽了,不管哪一个label更大:


当然,如果label都非常短,按钮都会等宽收缩。除非有其他约束阻止了,否则按钮尺寸将会精确的匹配它们的标题,不多,不少。我们怎么称呼这种情况?对的,就是固有内容大小。


固有内容大小

在有自动布局之前,你总是需要告诉按钮和其他的控件它们应该有多大,然后设置它们的frame或bounds,再或者在Interface Builder里恢复它们的尺寸。但是现在情况改变了。大多数控件完全可以根据内容来决定它们到底需要多宽。标签可以知道自己有多宽多高,因为它知道文本的长度和文字的大小。按钮也一样,可以根据文本,背景图和内边距等来决定大小。同样的,这对于segmented controls, progress bars和其他大多数的控件也都是有效的,不过可能其中有一些控件只能预判高度而不能知道它的宽度。这种特性被叫做固有内容大小,它对于自动布局是一个很重要的概念。你已经见过button的action了。自动布局会询问你的控件需要多大,并且会依据那些信息在屏幕上把控件画出来。通常情况下,你都会使用固有内容大小,但也有一些特殊情况你不想用它。那你就可以给控件设置一个准确的Width和Height约束来阻止固有尺寸大小的使用。设想一下,你在UIImageView上设置了一张图片,如果图片的大小超出了屏幕,会发生什么?你可以给这个image view一个固定的宽高和缩放比例,除非你想重定义图片的尺寸。


 如果你给按钮一个固定宽度的约束会发生什么?按钮会计算自己的尺寸,但是如果你给了一个固定的尺寸,它就不会再计算。选择靠上的按钮并且选择Pin\Widthfrom菜单。给按钮添加一个固定宽度(在按钮下方出现一个完整的T字架):


因为这种类型的约束只会对按钮本身有效,对它的父视图无效,所以在Outline文档中,它将会列在按钮对象下面,你可以固定这个按钮的宽度为46点。

你不能简单的拉伸按钮来调整大小。如果你这么做了,你最后只会得到一个橘黄色的虚线框。一定要记住,在Xcode5中不会自动更新约束(不像Xcode4)。因此,如果你改变了按钮的尺寸和位置,他会提示你需要让约束再次匹配。这是仅简单的改变约束的一种替代方法。

选择Width约束并进入Attributes inspector选项卡。把按钮的宽度约束改为80点:


运行app并点击按钮,发生了什么?按钮的文本改变了,但是它被删除了一部分,这是因为在按钮中没有足够的空间:

因为靠上的按钮有一个fixed-width约束,并且所有的按钮都要求一样的尺寸,所有它们就永远不会收缩或者扩展。

注意:你可能在设计中不想给按钮设置Width约束,那最好的办法就是让按钮使用固有尺寸,但是如果你遇到你想在app运行时改变控件的尺寸,但是却不能的情况。你就应该仔细检查一下是不是有fixed Width尺寸约束在作祟。

Play around with this stuff for a bit to get the hang of pinning and aligning views. Get a feel for it, (这句翻译不了,自行解决~)不是一切都能立刻就显而易见的,你只要记住必须有足够的约束自动布局才能预计算所有视图的位置和尺寸。


画廊例子

你现在可能会想约束到底是什么,你要怎么样才能通过建立不同视图之间的关系,从而构建起自己的布局。在下面一节中,你将看到怎么使用自动布局和约束来创建一个符合真实世界场景的布局。

让我们假装你想做一个浏览你图片的程序。它在横屏和竖屏中看起来就像下面这样:


屏幕被分成了4块,每一块都有一个image view和label。我们要怎么样才能达到这种效果呢?

让我们开始创作这个app吧。首先用Single View Application模板创建一个新的iphone项目,名字叫“Gallery”。

打开Main.storyboard。从Object Library中拖拽View到视图上。把尺寸设置为160点和284点,并且把它的背景色变为白色以外的其他颜色(例如:绿色):


注意:拖拽UIView到故事板上有两个原因:a)你要使用它来包含其他的控件视图,这些控件可以帮助你组织起场景的内容;b)它也作为一个自定义view或控制器的占位符,你可以设置它的Class属性,将它变为你自定义的UIView/UIControl的类(即继承UIView/UICtontrol的类)。

让我们给这些view一点约束。你已经看过加约束的两种方式:用Editor\Pin和Align菜单,还有按住ctrl在两个view之间拖拽。这里告诉你第三种添加约束的方法。在Interface Builder窗口的底部有一行这样的按钮: 


椭圆包围的四个按钮就是用于自动布局的。从左到右分别是:对其(Align),固定(Pin),解决自动布局问题(Resolve Auto Layout Issues)和重定义尺寸(Resizing Behavior)。前三个按钮鱼Editor菜单中的对应项有一致的功能。Resizing Behavior按钮允许你在重新设置view的尺寸的时候,改变已经添加的约束。

选择绿色的view并且点击Pin按钮。会弹出一系列你可以添加的约束:


 

顶部的Spacing to nearest neighbor是最经常用到的。点击者4个T字架,它们就会变成实体的红色:


这样就会给绿色的view和它的父视图的四边创建四个新约束。实际上的间距值因人而异,这要看你view的位置。(你不需要改变这些值来匹配我的图)。点击 Click Add 4 Constraints完成约束的添加

现在你的故事板看起来应该这样:


视图需要这4个约束来保证它的位置。UIView不像button或者label,它没有固有内容大小。你必须有足够的约束来保证每个view的位置和尺寸,view总是需要约束来告诉它,它需要多大。

你可能会奇怪,尺寸的约束在哪?在这个案例中,view的尺寸约束是隐含在父视图的尺寸中。布局中有两个Horizontal Spaces约束和两个Vertical Spaces约束,这样就能保证固定的长度。你在Outline文档中可以看到他们:


绿色view的宽是由这个公式计算出来的:“父视图的宽-(98+62)”,它的高是:“父视图的高-(65+199)”。这样,间距就被固定了,你的view除了重新定义尺寸之外别无他法。(你的值可能会有所不同,它依据于你的view放的位置)。

当你旋转这个app的时候,父视图的规格就会从320X568变为568X320。公式就会重新计算宽和高,这时候你会得到新的绿色view的尺寸(408X56)。

你可以旋转你自己的试试,你也可以在Interface Builder直观的模拟它们。打开Assistant editor(按Xcode工具栏中看起来像 外星人/男管家 的那个按钮)并且在弹出的菜单中选择Preview:


单击底部的箭头按钮可以改变界面的方向。这样就能立刻给你一个横屏的故事板布局预览。绿色view会重定义尺寸以满足横向间距和纵向间距的约束。

你可以留下这个预览窗口,当你设计UI时,它会自动改变。你也可以在3.5吋和4吋屏之间切换。

注意:你可能会奇怪为什么顶部的约束没有顶到屏幕的最上面:


而是停在了状态栏处。这是因为在ios7中,状态栏总是绘制在view控制器的顶部 - 而不再是一个单独的bar。这样做有什么用?当你创建约束时,它无法确切的依附在屏幕顶部,但是顶部会有一个看不见的行,它叫做 Top Layout Guide。

在一个正常的view控制器中,这个guide距顶部有20点,至少在状态栏不隐藏的时候是这样的。在导航控制器中,它位于导航栏下面。因为导航栏在视图中有不一样的高度, Top Layout Guide会在设备旋转时随着导航栏移动。这样就很容易的得到view和导航栏的相对位置。同样的,底部也有Bottom Layout Guide,用来放置标签栏和工具栏。

有时候,可能你不想在设备旋转时重设你的UIView的尺寸,因此你可以给这个view一个固定的宽高约束。现在,让我们来试试。选择绿色的view并点击Pin按钮;在弹出窗口中给width和height打上勾。


点击Add 2 Constraints来完成约束的添加。现在你给视图添加了两个新约束,一个是宽度160点的约束,一个是高度284点的约束:


因为宽和高仅应用在当前视图,所以它们在Outline文档中位于自己的view下面。一般来说,约束都表达了两个不同view之间的某种关系 - 例如:横向间距和纵向间距(Horizontal and Vertical Space)约束就是绿色视图与它父视图的关系 - 你也可以认为宽度和高度约束是绿色视图和它自己的约束。

运行app,噢,看起来多棒啊。现在把你的屏幕横置,哎哟!没有像你想的那样 - 视图的尺寸再次该变 - 而是报错了,Xcode给出了一堆错误的信息:


Gallery[39367:a0b] Unable to simultaneously satisfy constraints.
        Probably at least one of the constraints in the following list is one you don't want. Try this: 
     (1) look at each constraint and try to figure out which you don't expect; (2) find the code that 
     added the unwanted constraint or constraints and fix it. (Note: If you're seeing 
     NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for 
     the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0xc1a1e80 V:[UIView:0xc1a2b10(284)]>",
    "<NSLayoutConstraint:0xc1a36c0 V:[_UILayoutGuide:0xc1a2d20]-(65)-[UIView:0xc1a2b10]>",
    "<NSLayoutConstraint:0xc1a36f0 V:[UIView:0xc1a2b10]-(199)-[_UILayoutGuide:0xc1a3230]>",
    "<_UILayoutSupportConstraint:0xc15dbd0 V:[_UILayoutGuide:0xc1a2d20(20)]>",
    "<_UILayoutSupportConstraint:0xc1a1510 V:|-(0)-[_UILayoutGuide:0xc1a2d20]   (Names: '|':UIView:0xc1a2930 )>",
    "<_UILayoutSupportConstraint:0xc1a3720 V:[_UILayoutGuide:0xc1a3230(0)]>",
    "<_UILayoutSupportConstraint:0xc1a30e0 _UILayoutGuide:0xc1a3230.bottom == UIView:0xc1a2930.bottom>",
    "<NSAutoresizingMaskLayoutConstraint:0x8c6c6a0 h=--& v=--& H:[UIView:0xc1a2930(320)]>"
)
 
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0xc1a1e80 V:[UIView:0xc1a2b10(284)]>
 
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
 
. . .

你还记得我说过“必须有足够的约束,自动布局才能计算所有视图的位置和尺寸”么?好吧,在这个例子中,约束又太多了。当你看到“不能同时满足所有约束(Unable to simultaneously satisfy constraints)”的错误的时候,那就意味着你的约束之间有冲突。

让我们再看一下哪些约束:


绿色视图上设置了6个约束,4个间距约束(1-4)和你刚刚设置的宽高约束(5和6)。冲突在哪呢?

在竖屏的时候不应该有问题,因为在数学上是满足的。父视图的宽是320点。如果你添加了Horizontal Space和Width约束,你应该保证它们的总长度小于等于320点。这是我计算视图位置的方式:98+160+62 = 320。同样的,所有竖向约束相加以后就应该是568点。

但是当你旋转设备为横屏时,窗口(指的父视图)就变成了568点宽。这意味着98+160+62+? = 568。这里额外多了248点,无法满足方程式,所以自动布局不知道从哪里来得到这248点。同样的,对于竖向约束也是一样。

解决冲突的办法有两个:第一是保持view的宽度固定,但外边距必须可变。第二是保持外边距固定,但宽度必须可变。你不能同时固定它们。你要去掉这些约束中的其中一个。在上面的例子中,若你想要在横屏竖屏中都拥有同样的宽,那Horizontal Space就必须去掉。

移除右边的横向间距和下边竖向间距。故事板上会显示成这样:


好了,现在view就有一个正确约束来预判它的尺寸与位置了 - 不多,不少。运行app来查证一下错误信息是否已经没有了,然后这个view在旋转以后是否能保持宽度。

注意:尽管Interface Builder可以很好的警告你有无效的约束,但它不是神。它只会警告你:噢,你的约束不够。但是如果你约束太多了,它就无法检测出来了。不过至少在出错的时候,自动布局还是会给出一个详细的错误信息。如果你想要学习更多关于如何分析错误信息和诊断布局问题,那你可以看 iOS 6 by Tutorials中的“Intermediate Auto Layout”。

竖屏绘制

拖拽一个label到绿色的view上。注意看,现在参考线出现在了绿色view的内部,因为它是label的父视图。


调整label的位置到参考线的下边距,横向居中的地方。给label下面添加与绿色view之间的间距约束,有20点的距离。快速的方式是使用Pin按钮,仅选择下面的T字架:


现在给label添加横向居中的约束。你已经试过Editor\Align菜单了,但你也可以使用自动布局菜单的Align按钮。选择label并点击Align按钮,得到一个弹出窗:


在Horizontal Center前打上勾然后点击Add 1 Constraint。这时候,故事板应该是这个样子的:


注意这两个新的横向和纵向间距约束是位于绿色view自己的约束列表里,而不是在主视图里。

拖拽一个新的Image View对象到故事板上,让你的布局看起来像这样::


这个图片视图固定了上,左,右边缘在父视图上,但下部以标准的8点间距连接在了label的顶部。如果你不确定要怎么做,那就跟着下面的步骤走:

1. 拖拽image view到绿色视图中,现在不用担心它的尺寸和位置:


2. 选中image view,按Pin按钮选择下面的选项:


上,左,右的T字架设置为20点,但是下面的设置为8点。重点:对于Update Frames,你应该选择Items of New Constraints。如果你左边距已经默认满足要求,故事板看起来就是这样的:


上面这个约束是你选择了一个不一样的frame。如果你选择了Items of New Constraints, Interface Builder将自动的调整画面来添加约束,一切看起来都很棒:


当然,如果你选错了frame,你也可以使用Resolve Auto Layout Issues button来修复它:


下载这个教程资源并解压这个文件。你就会找到一个图片文件夹 - 添加这个文件夹到你的项目。设置Ray.png作为这个image view的图片,改变image view的模式为Aspect Fit并且设置它的背景色为白色。把label的文本改为“Ray”。

你的布局应该是这样的:


你可能注意到绿色view内部的约束突然变成了橘黄色。这发生在你给image view设置图片的时候。你的布局为什么突然无效了?幸运的是你可以带着你的猜想来让Xcode告诉你为什么错处了。

点击这个这个红色的靶子,它位于View控制器的Outline文档中:


你会看到一个Content Priority Ambiguity(内容优先级歧义)的错误。这意味着:如果image view和label都没有固定的高度,那自动布局系统就不知道应该怎么分配大小。(Interface Builder似乎会忽略已经设置过的固定高度约束)

让我们把绿色的view的高度变为100点试试。自动布局是怎么将这100点分配给view内部的label和image view的?是label保持尺寸而image view变成100点高了么?还是label变高了而image view保持尺寸?还是他们各自被分配了50点,又或者划分成25/75,40/60,又或者其它的什么结合?

如果你不解决这个问题,那么自动布局系统就会去猜测,就可能导致结果莫名其妙。

恰当的解决办法是改变label的“Content Compression Resistance Priority”。你将从随后内容中学习到更多。现在,打开label的Size inspector,设置Content Compression Resistance Priority的vertical为751。这样就使它的优先级高于image view。然后继续设置Content Hugging Priority为252。


这时候T字架就会再次变成蓝色,自动布局系统的警告也会消失。

添加到另一个顶端

拖拽绿色的view到主视图的左上角。回忆一下之前做过的,绿色的view已经有Horizontal Space和Vertical Space约束来控制它在父视图中的位置了。现在,这些约束依然存在,它们导致了视图的frame无法对齐参考线。


为了修正它,使用Resolve Auto Layout Issues按钮并选择Update Constraints。之前你使用的Update Frames,用来移动和重定义view的尺寸来匹配你的约束。现在这刚好是一个相反的操作:你会用你的约束来匹配view的frame。

你可能注意到在顶部的Vertical Space现在是负的。这种情况是因为约束链接到了Top Layout Guide(顶部参考线)。不要问为什么,没有规定说约束值不能是负的,你可以就像这样留下它。(如果你看着碍眼,那就删掉 “Vertical Space (-20)”约束,然后把view重新绑定到窗口的顶部)

Horizontal Space现在为0,表现为一条紧贴窗口左边缘的粗蓝线。尽管view已经位于角落里了,但是它任然需要约束来固定住它:


选择绿色的视图按下⌘D来复制它。移动复制的视图到右上角:


注意T字架现在是橘黄色的。当你复制它的时候,它已经上丢失了X,Y坐标的约束。为了修正它,固定view到窗口的右、上边缘。

再复制两次,分别把复制的视图放在坐下角落和右下角落。然后再把他们固定在他们该在地方。

改变后的场景如下:


 

哈哈~他们看起来好像都是程序员 :-)

运行app,在竖屏上很棒,但是横屏上可能不是那么好:


本来应该很棒的战士却变糟了:你给4个色彩鲜艳的容器view设定了固定的宽和高,因此它们就总是保证这样的尺寸,而不管它们的父视图的尺寸如何。

从这4个view下,选择Width (160)和Height (284)的约束并删除它们(在Outline文档中很容易完成)。如果你现在运行app,你会看到:


注意:如果你很奇怪为什么有的view变得比其他的大了,那我告诉你,这是固有内容尺寸的原因。图片的尺寸决定了image view有多大。文本的尺寸决定了label有多大。再加上四边的20点外边距,所有的尺寸加起来决定了每个视图的尺寸。

这看起来很像你的上一部分解决的问题,因此你要向前思考,你可能回忆起来了,你要让每个view的宽和高都相等。

选择这4个view。Outline大纲里很容易完成;按住⌘点击这4个view。你就可以添加这个约束。在弹出窗中,给Equal Widths和Equal Heights打上勾,然后点击Add 6 Constraints。


再次运行这个app并且旋转设备。噢...还是不咋地:


所有的view都有同样的高,并且他们也有同样的宽,你的约束是对的。但这个宽和高可能不是你想要的。

光告诉自动布局系统必须有同样的尺寸是不够的。因为自动布局系统不知道这4个view是互相连接的。它们的边贴边的设计,但在它们之间没有这样的约束。自动布局就不知道它需要在“Ray”个“Matthijs”之间划分窗口。

如果自动布局系统自己不能完成,那你就要告诉它。


选择Ray和Matthijs的视图,从编辑菜单选择Pin\Horizontal Spacing。因为view之间是边贴边的,那在它们之间就要加上尺寸为0的Horizontal Space约束。好了,现在有足够的约束让自动布局系统了解两个view之间的关系了。再给Ray和Dennis Ritchie视图之间使用Editor\Pin\Vertical Spacing。

再运行app,现在看起来就像这样:


注意:Interface Builder任然会抱怨view的位置不对。我不确信为什么会发生这种情况,可能是Xcode有bug吧。如果这些警告信息让你不爽,那就选择主视图(或view控制器)并且从Resolve Auto Layout Issues菜单选择Update All Frames in View Controller。不过你的app运行时这种改变不会起作用,但至少让Xcode看起来很舒心。

image view上有一点要注意:它们可能会延伸了,因为你没有给它们一个固定尺寸。你可能不知道,这是有意这样做的。image view不能适配横屏的模式。如果你想保持image view的原始宽高比,那很不幸。在Interface Builder中你是无法得到下面这样的效果的:






Demo代码:

不幸的是,Interface Builder不能正确的提供用约束保持view的原样宽高比。如果要这么做,你就需要自己编程。你可以在iOS 6 by Tutorials的“Intermediate Auto Layout”中学习如何操作。

我该去哪继续学习?

如果你全都看完做完了,祝贺你 - 你现在知道自动布局是什么了,并且有了一定的实践基础!但你任然有许多需要学习...

这个教程的第一部分你可以从iOS 6 by Tutorials一书中的自动布局章节阅读到。第二部分会教你如何使用自动布局来创建更多“真实世界”的场景布局,你可以从Interface Builder学习一切你想知道的自动布局的知识。

但是就像其他的视觉设计工具一样,Interface Builder也有自己的局限性。有时,它只能通过NSLayoutConstraint对象来从代码实现。IOS 6的教程有一个章节包含了这个主题所有的内容,Intermediate Auto Layout。因此,如果你想知道自动布局的后半部分,请买这本书吧!(翻译到最后,发现居然带广告,为了尊重原作者,还是给出连接吧~~~)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值