这篇教程的前半部分被翻译出来很久了,我也是通过这个教程学会的IOS自动布局。但是后半部分(即本篇)一直未有翻译,正好最近跳坑翻译,就寻来这篇教程,进行翻译。前半部分已经转载至本博客,后半部分即本篇。学习IOS自动布局的朋友可以看看。自动布局很强大。
转载请注明来源:http://www.cnblogs.com/zer0Black/p/3977288.html
作者:zer0Black
这篇教程绝对的最好的学习IOS自动布局的文章,没有之一
原文地址:Beginning Auto Layout Tutorial in iOS 7: Part 2
正文如下:
请注意: 团队成员Matthijs Hollemans (IOS学徒系列的作者) 已经将这篇教程移植到iOS 7 feast. 我们希望您喜欢!
在开始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. |
你还记得我说过“必须有足够的约束,自动布局才能计算所有视图的位置和尺寸”么?好吧,在这个例子中,约束又太多了。当你看到“不能同时满足所有约束(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中你是无法得到下面这样的效果的:
不幸的是,Interface Builder不能正确的提供用约束保持view的原样宽高比。如果要这么做,你就需要自己编程。你可以在iOS 6 by Tutorials的“Intermediate Auto Layout”中学习如何操作。
我该去哪继续学习?
如果你全都看完做完了,祝贺你 - 你现在知道自动布局是什么了,并且有了一定的实践基础!但你任然有许多需要学习...
这个教程的第一部分你可以从iOS 6 by Tutorials一书中的自动布局章节阅读到。第二部分会教你如何使用自动布局来创建更多“真实世界”的场景布局,你可以从Interface Builder学习一切你想知道的自动布局的知识。
但是就像其他的视觉设计工具一样,Interface Builder也有自己的局限性。有时,它只能通过NSLayoutConstraint对象来从代码实现。IOS 6的教程有一个章节包含了这个主题所有的内容,Intermediate Auto Layout。因此,如果你想知道自动布局的后半部分,请买这本书吧!(翻译到最后,发现居然带广告,为了尊重原作者,还是给出连接吧~~~)