Storyboard入门到精通:自动布局

来源:互联网 发布:飞控led软件下载 编辑:程序博客网 时间:2024/05/22 12:48
Matthijs Hollemans

这篇文章还可以在这里找到 英语, 韩语

Learn Auto Layout in the iOS 6 SDK!

来自Ray:恭喜各位!你们已经通过宣传ios feast提前解锁了第一个有关IOS6的教程。

目前这份教程只是我们的新书iOS 6 By Tutorials里面某个章节的精简版。这份教程由同样著作过iOS Apprentice Series 的Matthijs Hollemans 完成,开始体验吧!

这份教程由IOS 教程小组的组员 Matthijs Hollemans发布,Matthijs 既是一位经验丰富的IOS程序员又是一名资深老到的界面设计者。

你是否曾经因为尝试想让你的应用同时在景观方向(横版)以及肖像方向(竖版)看上去不错而感到受挫?是否为了让应用同时支持iPhone以及iPad的模型尺寸而抓狂?现在我们可以不用为此而担心了,我有个好消息带给大家!

通常来说,如果屏幕是固定尺寸,那么设计它的用户界面不会很难,但如果屏幕的frame需要能够变化,那么其中各个UI元素的位置以及尺寸也必须为了适应新的尺寸做相应的变化。

目前为止,即使你的界面设计是在合理的复杂度内,你也必须要为之写许多代码来适应变化的布局。现在我相信你会很高兴听到这种情况将不会发生了-对于iPhone与iPad IOS6 带来了一个非常了不起的特征:自动布局。

自动布局不仅能给你的应用带来各种屏幕尺寸设计的支持,做为额外的惊喜,它还能使设计中的各种小事比如多语言环境支持。你从此不必再为你想要支持的各种语言重新设计nibs和storyboards文件,当然这也包括一些从右至左书写的语言比如说希伯来文和阿拉伯语。

这篇教程将向你展示的是如何开始使用Iterface Builder来做自动布局。在iOS 6 by Tutorials里,我们把这篇教程内容写得更深,并且基于这个知识会有一个全新的章节,在这里面你会看到如果通过代码来释放自动布局的全部功能。

好吧,现在开始拿着你喜欢的零食以及饮料,准备开始做一名自动布局的大师吧!

springs and struts 的问题

毫无疑问你可能对autosizing masks比较熟悉–这个也就是 “springs and struts” 模式。autosizing mask决定了一个view会发生什么当它的superview 改变大小的时候。它是否有灵活并且自动修复页边处理能力(the struts),它的宽和高同时也会发生什么变化呢(the springs)?

举个例子,当一个view的superview的宽度变宽时,它的宽度也会灵活地跟着变宽,并且它的右边界也会自动修复般的一直紧挨着superview的右边界。

autosizing 系统处理这种简单的情况还是不错的,但是当情况稍微复杂一点的时候,它就会很快搞砸你的布局。现在让我们看一个springs and struts模式所不能处理的一个简单例子吧。

打开Xcode创建一个基于Single View Application template新项目,把之命名为”StrutsProblem”,选择iPhone程序并且禁用Storyboards:

Project options

在 Interface Builder 里点击打开ViewController.xib。在你做任何其他事情之前,请先在nib里把Auto Layout禁用掉。你可以在File inspector里找到这个选项:

Disable autolayout

取消选择“Use Autolayout”复选框. 那么现在你的nib使用的是旧版本的 struts-and-springs 模式。

提示: 任何你通过Xcode4.5或者更高版本创建的新nib或者storyboard文件会默认使用Auto Layout。因为Auto Layout这个特性只有在IOS 6中有,所以如果你想要使用 Xcode4.5来做一些兼容IOS5的应用,你必须要在新的nib或者storyboard文件中通过取消选择“Use Autolayout”复选框来禁用Auto Layout。

拖拉三个新的view到main view中,如图所示:

Portrait design

为了使看起来更清晰,我们把每个view都填注颜色。

现在每个View都离窗体边界 20 points远;各个填充颜色的view之间的距离也是20 points。底部的view是280 points宽,并且顶部两个view都设置成130 points宽。所有的view都设置成200 points 高。

运行程序并且把模拟器或者你的设备旋转至景观方向。你的设备会如下图所以,和我们理想的差距甚远:

Landscape looks bad

提示:你能够通过使用 HardwareRotate Left and Rotate Right的菜单选项来旋转模拟器, 或者通过按住Cmd然后使用向左或者向右方向键来旋转。

而我想要的是让程序运行后是这个样子的在景观方向下:

Landscape good

很明显, autosizing masks 对于要达到这三个view的理想变化还需要做点其他的。 从左上角的View来开始设置autosizing :

Autosizing top-left view

这一步使View能紧挨着顶部和左边缘(而不是底部与右边缘),并且在水平和垂直方向上都能够支持伸缩当superview改变其大小时。

类似地, 改变 右上角autosizing 设置:

Autosizing top-right view

这是底部view的设置:

Autosizing bottom view

运行程序并且转动设备至景观方向。现在应该看上去是这样:

Landscape looks bad (2)

和理想的很接近了,但是还有点瑕疵。三个view之间的距离是不正确的。另外仔细看,这三个view的尺寸也不是100%正确. 造成这个原因是autosizing masks虽然知道要改变view的尺寸当superview改变时,但是它不知道具体该改变多少尺寸。

你可以玩一下 autosizing masks-比如说,改变可以改变的宽和高的值(“springs”)- 但是你几乎不可能精确设置到20-points的距离在三个view之间。

Why?!?!?

为了解决使用 the springs and struts 方式改变布局所造成的问题,很不幸的,你必须要写一些代码来做。

UIKit 会发送一些消息到你的view controllers当用户界面在开始旋转前,在旋转过程中以及旋转后。你可以通过监听这些消息来改变你用户界面的布局。通常你会重写willAnimateRotationToInterfaceOrientation:duration:来改变任何需要重新规划的view的frame。

但在你开始做这之前, 你首先需要声明views里面的outlet 属性。

Xcode切换到 the Assistant Editor 模式(在Xcode工具栏的右上角的编辑器工具包的中间一个按钮)然后把每个view拖拉至view controller:

Ctrl-drag outlet property

逐个把这些view与属性连接起来:

@property (weak, nonatomic) IBOutlet UIView *topLeftView;@property (weak, nonatomic) IBOutlet UIView *topRightView;@property (weak, nonatomic) IBOutlet UIView *bottomView;

Add the following code to ViewController.m:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation                                          duration:(NSTimeInterval)duration{    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];    if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft    ||  toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)    {        CGRect rect = self.topLeftView.frame;        rect.size.width = 210;        rect.size.height = 120;        self.topLeftView.frame = rect;        rect = self.topRightView.frame;        rect.origin.x = 250;        rect.size.width = 210;        rect.size.height = 120;        self.topRightView.frame = rect;        rect = self.bottomView.frame;        rect.origin.y = 160;        rect.size.width = 440;        rect.size.height = 120;        self.bottomView.frame = rect;    }    else    {        CGRect rect = self.topLeftView.frame;        rect.size.width = 130;        rect.size.height = 200;        self.topLeftView.frame = rect;        rect = self.topRightView.frame;        rect.origin.x = 170;        rect.size.width = 130;        rect.size.height = 200;        self.topRightView.frame = rect;        rect = self.bottomView.frame;        rect.origin.y = 240;        rect.size.width = 280;        rect.size.height = 200;        self.bottomView.frame = rect;    }}

当view controller旋转至一个新的方向时会调用这个回调函数。现在当用户界面的方向转动时view controller使它里面的view尺寸缩放理想了- 这是一种建立在对iPhone屏幕尺寸了解上的硬编码能力。因为这个回调函数发生在一个动画block里,所以当改变它的尺寸会有动画效果。

等等,现在还不能运行程序。 你必须先恢复这三个view的autosizing masks 设置如下图所示,否则 autosizing 机制会与你在 willAnimateRotation: 函数设置里的view的位置、尺寸产生冲突。

Autosizing off

现在可以运行程序了,然后将设备翻转至景观方向。可以看到每个view的呈现都很理想,再次翻准屏幕至肖像方向,看上去也还不错。

这样做成功了,但是为了这么个简单的呈现你就要必须写许多代码了。想象一下,当你遇到真正更加复杂,特别是动态的那些独立View改变尺寸,或者有一系列的subviews没有被固定时你在代码上所需要作出的努力。

There must be another way

提示: 你还可以使用另外一种方法,那就是同时做好肖像方向以及景观方向的nib,然后当设备转动时,把对应的view从nib文件载入进来,把当前的view交换出去。但即使这样,你还是需要做很多的工作,另外你还多了同时管理两个nib文件而不是一个nib文件的麻烦。

Auto Layout 来拯救了!

现在我将要展示的是如何用Auto Layout来做到同样的效果。首先,把willAnimateRotationToInterfaceOrientation:duration:这个方法从ViewController.m里面删除,因为目前我要做的Auto Layout是不需要写任何代码的。

回到ViewController.xib然后在File inspector控制面板里,把“Use Autolayout”的复选框勾上使Auto Layout对这个nib文件起作用:

Enable autolayout

提示: Auto Layout 功能在整个nib或者storyboard文件里总是被开启着的。在这两种文件里的所有view都会使用Auto layout功能如果你把勾选上的话。

现在运行程序并且转动屏幕,呈现的样子还是之前的混乱样。

Landscape looks bad

现在让我们启动Auto Layout功能. 按住Cmd键同时选中顶部的两个view (绿色的以及黄色的),。 从 Xcode的Editor 菜单, 选择PinWidths Equally:

Pin widths equally

再次重新选中这两个view并且做 EditorPinHorizontal Spacing操作 (即使这两个view看上去像被选中了当你做第一个Pin操作后。但是请注意目前他们处在一种特殊的布局关系显示模式中,你还是必须要重新选中这两个view。)

在左边的文档概要图中, 你会注意到有一个新的section名叫 “Constraints”. 这个section 会被自动加入当你在nib文件中启用Auto Layout时。在这篇文档的下一部分你会了解到这些Contraints是什么以及他们是如何操作的。

现在, 我们把一个名叫  “Horizontal Space (170)” 的从Constraints列表里面删除:

Horizontal space constraint

运行程序并转动屏幕. 现在看上去好多了 – 顶部的两个view 有了合适的宽度和间距 – 但还不是我们想要的样子:

Landscape almost there

按住Cmd键同时选中所有的三个view。 在菜单栏, 做PinHeights Equally 操作

现在还是按住Cmd键同时选中左上角的以及底部的view,然后做EditorPinVertical Spacing 操作

最后,把“Vertical Space (240)” 从constraint列表里面删除。

如果你一下子同时选中所有的三个view,Interface Builder应该如下图所示:

The constraints

蓝色的T型状对象定义了各个view之间的限制。这看上去有点复杂, 但是你一旦学会了,你会发现这种表达相当简洁明了。

运行程序 … 哇, 没有写一行代码每样东西都看上去非常棒了!

Landscape good

酷, 但刚才你究竟做了什么呢?Auto Layout 能使你简单地表达清楚页面布局中的各个view之间的关系而不会让你为了各种view有多大以及他们该定位在哪里硬编许多代码 。

你刚才做了如下的关系操作 – 也就是 constraints – 在页面布局里:

  • 左上角和右上角的view (也就是第一次的pin widths equally 操作).
  • 在左上角view和右上角view之间有20-point的间距 (相应的操作是 pin horizontal spacing).
  • 所有的view是相同的高度 (相应的操作是pin heights equally).
  • 在顶部两个view与底部的view之间有一个20-point的间距 (the pin vertical spacing).

以上这些就足以展示,当屏幕尺寸变化时,Auto Layout如何放置布局里的各种view以及它是如何工作的。

Well done

提示: springs-and-struts布局模式也会带来一些其他限制当你从它切换至“Use Autolayout”模式时。对于各个view和屏幕边缘之间的边距都基本会有一条限制,是这么说的:“这个view总是和顶部/底部/左边/右边保持着20-points的距离。”

你可以看到你的所有contraints在文档概要里。如果你在文档概要里点击一个constraint,Interface Builder会在contraint在view中所体现的地方通过画一条白色的边框并且对之添加一个阴影使其高亮显示:

Selected constraint

Constraints是真实的对象 (属于 NSLayoutConstraint类) ,他们也拥有相应的属性。 比如说,选中顶部两个view间距的constraint(名为“Horizontal Space (20)”)然后切换至它的Attributes inspector。 现在你可以通过修改Constant里的值来改变两个view之间的距离大小。

Constraint attributes

将之设置成100并且运行程序。现在两个view之间的距离更宽了:

Landscape wider margin

当你的程序需要描述各种view的布局时,Auto Layout比起springs and struts表达能力要强许多。在这份教程的余下部分,你将会学到关于constraints的所有以及如何在Interface Builder应用之并使其能做到各种布局安排。

Auto Layout的工作原理

就像你在上面看到的测试一样, Auto Layout 的基本工具就是constraint. 一个constraint 描述了两个view之间的几何关系。比如说,你可能有一个constraint是这样的:

“Lable A的右边界和和Lable B的左边界以20 points的空白相连接。”

Auto Layout用所有的这些constraints来对你所有的view做一些数学计算以致view达到一个理想的位置以及尺寸。 你不再需要自己来设置view的frame-Auto Layout会帮你做这一切-完全基于你对view所设置的constraints。

在没有Auto Layout之前,你总是通过硬编码来设置view的框架, 也可能通过Interface Builder 坐标里面精确的放置他们,通过initWithFrame:来传递一个矩形,或者通过设置view的frame,bounds或者center 属性。

对于刚刚你做的应用, 你特地把frames设置到下图所示:

Struts coordinates

你也可以对每个view做autosizing masks设置:

Struts autosizing masks

那应该不再是你认为的屏幕设计方式了。使用Auto Layout,你所要做的只是如下图所示:

Auto Layout instead of struts

现在对于view来说尺寸和位置已经不那么重要了; 把问题都交给constraints吧。当然, 当你在canvas里拖进一个新的按钮或者标签时,这个控件会有一个特定的尺寸然后你把它放到一个特定的位置, 但这也只是一种设计目的来告诉Interface Builder在哪里放置constraints。

设计成你想要的样子

对你来说一个巨大的优势来使用constraints是你不再需要摆弄坐标系统来使你的各个view出现在合适的位置。你要做的只是通过对Auto Layout描述每个view之间的联系关系。这种设计方式我们叫做 通过目的来设计.

当你通过目的来设计时, 你在表达的是你想要达到的目标是什么而不是如何来完成目标。在以前会是这么个说法:“这个按钮的左上角的坐标是(20, 230)”, 现在你可以这么表达了:

“这个按钮在它的superview中垂直居中,把他放置在离它的superview左边缘的一个固定位置。”

使用这个描述, Auto Layout能自动计算出你的按钮该出现在哪里, 无论它的superview的大小。

这里是其他通过目的来设计的例子(Auto Layout 能够应付所有的这些指示):

“这两个text fields应该保持同样大小。”
“这两个button应该保持同时移动。”
“这四个标签应该同时保持右边对齐。”

这就使你的用户界面设计显得更加具有描述性。你只需简单的定义constraints, 然后系统会自动帮你计算frames。

在第一部分你看到了,要想让一个只有几个view的页面布局同时在iPhone的两个方向显示的合适所需要做到的很多工作。但如果你用Auto Layout来做的话就可以省去那一方面的力气了。如果你对constraints设置的恰当,那么布局会自动恰当显示而不需要你对肖像方向和景观方向的view做一丝变化。

对于使用Auto Layout另外个重要的好处是国际化。 比如说德文字符, 是出了名的长,把它放进你的标签会是一件十分头疼的事情。 但是再一次,Auto Layout会来拯救你,因为它能够帮你自动缩放基于内容需要表现的标签-除此以外的每样东西还是会根据constraints来调整。

添加对德文, 法文, 以及其他的语言的支持要做的只是简单设置你的constraints ,translating the text,仅仅是这样哦!

French

Auto Layout 最好的入门方式就是和它玩。这也就是这份教程余下部分要讲到的。

提示: Auto Layout is 不仅仅对方向旋转有帮助;它也能很简单的拉升你的用户界面来适应不同的屏幕尺寸。这不是一个巧合吗!当iPhone 5的长屏幕出现时这项技术正好被加进了ISO里 了。 Auto Layout 使得你在填满iPhone 5的多余垂直屏幕内容时变得非常简单。并且天知道会不会有一个传言中的 “iPad mini”出现… 至少你现在可以用Auto Layout 来为将来做准备了。

爱上constraints

关掉你目前的工程然后创建一个新的项目使用Single View Application模板。命名项目为“Constraints”。然后选择为iPhone project并且不使用storyboards,但是我们需要用到ARC。

一个使用Xcode4.5创建的新项目会默认为你选择启动Auto Layout,所以你必要做任何特别的事情来启用它。

点击ViewController.xib 来打开Interface Builder。把一个新的圆角按钮拖进canvas。 注意当你拖拽的时候,蓝色虚线会出现。这些线被认为是 guides:

Guides

这些guides会显示在屏幕的页边, 也会显示在中心:

Other guides

如果你之前使用过Interface Builder,那你毫无疑问会对这些guides很熟悉。当你想要对其东西时他们会非常有帮助。而当Auto Layout 启用时,这些guides有了不同的意义。你当然还是需要他们来帮你对其东西,但同时他们也会告诉你新的constraints会体现在哪里。

把button对着guides放到左上角。 现在的nib文件会看上去像这样:

Button with guides

看,有两个蓝色的东西附属在按钮上。 这些 T型状对象就是设置在这个按钮上的constraint。

所有的constraints 也都列在Interface Builder的左边的文档概要面板里:

Button constraints in document outline

目前我们有两个constraints, 在button和main view的左边缘之间有一个Horizontal Space, 在button和main view的顶部有一个Vertical Space。这层关系被constraint表示为:

“这个按钮会一直待在它的superview的左上角。”

现在再次选中这个按钮并把它放到nib文件右上角,还是对着蓝色guides:

Button top-right corner

现在 Horizontal Space 的constraint值改变了。它不再依附在按钮的左边界而是右边界了。

当你对着guides放置一个按钮(或者其他什么view)的时候,你会得到一个标准的大小,这个是被定义在“HIG”里面,这是苹果公司的iOS Human Interface Guidelines文档。对于屏幕的边缘的页边距,标准的大小是20-points的空白。

甚至假设你把button放在某些没有guide的地方,你还是会得到一个Horizontal or Vertical Space的constraint。试一下。然后把按钮像左挪一点,得到如图所示的效果:

Button larger H space

目前还是有一个 Horizontal Space的constraint。在文档概要图里,你能看到现在没有一个标准space了。

Larger H space document outline

你的按钮放在哪里,你就会得到一个对应的constraint。

还有一个“center”的constraint.。把按钮拖到canvas的底部中心处,让他正好能卡到中心guides:

Button bottom center

请注意这个Horizontal Space constraint现在被一个Center X Alignment constraint取代了,这也就意味着这个按钮会一直跟着它的superview中心对其在水平轴上。依然有一个Vertical Space constraint值保持着这个按钮待在view的最底部。(还是请使用标准页边距)。

运行程序并且转动设备至景观方向。看,甚至在景观方向,这个按钮还是呆在底部的中心位置:

Button at bottom center in landscape

对于这个按钮,这是你想要表达的目的是: “这个按钮应该一直待在底部的中心位置。” 请注意,现在没有任何地方你必须要告诉Interface Builder你的按钮的坐标是什么, 你只要把它放置的view中就行了。

有个Auto Layout,你可以不用在关心你的view在canvas里面的精确坐标位置了。所有这一切,Auto Layout会帮你从你设置的constraints里面派生出来(或者说是Interface Builder为你设置了这一切)。

在这个范例里你可以看出这个按钮在Size inspector里面的转化,那是相当的大啊:

Different size inspectors

当Auto Layout 禁用时,在X,Y,Width或者Height里的值会改变所选中view的位置以及尺寸。当Auto Layout 启用时,你还是能够在这些框里面输入新的值,但通常结果不会你是想要的效果。那个view 会移动,但是Interface Builder也会基于你的新值来计算出新的constraints。

比如说,把Width的值改到100,canvas里面的按钮会变成下图这个样子:

Button with fixed width

现在 Center X Alignment constraint的值消失了, 取而代之的是一个把按钮连在屏幕左边缘的Horizontal Space,这个按钮同时也会产生了一个新的constraint,它强制使按钮的宽度固定在100 points(可以看到按钮下方的蓝色栏)。

你在文档概要图的左边看到现在有了一个新的Width constraint:

Width constraint document outline

不像其他的constraint,那些是在按钮和它的superview之间,这个宽度constraint只能应用于按钮本身。你可以认为它是一个按钮和按钮之间的constraint。

拖动按钮使它再次卡在 Center X Alignment constraint 上。

小贴士: 因为通过 Size inspector来改变位置和大小可能会搞乱你的constraints,我建议尽量不要这么做,如果你非要改动布局,请更改constraints。

你现在可能想知道为什么按钮之前没有一个Width constraint。在没有的情况下,Auto Layout是如何知道要改变按钮的长度的呢?

可以这么解释:这个按钮自身知道他的宽度应该是多少,它通过基于它里面的标题文字外加上一些圆角的边距填充,可以计算出来的。如果你设置了一个按钮的背景图片,它也会把这一点计算在内的。

这个现象被认为是固有内容尺寸。不是所有的空间都会这样,但是大部分是这样的(UILable不在内)。如果一个view能够计算出它自己的首选尺寸,那么你就没有必要对其专门设置Width or Height constraints 了。关于这个以后你就看得多了。

I am not fat

为了得到按钮的最佳尺寸,选中它并且在Editor菜单里将至设置为Size to Fit Content 。这步操作会使按钮摆脱明确的Width constraint 并且将之恢复为按钮的固有内容尺寸模式。

两个按钮的探戈

Guides 不仅可以出现在view与superview的, 也可以出现在同一阶层的多个view之间。为了证明这点,现在请在canvas里面拖进一个新的圆角矩形按钮。

如果你把这个按钮放得离另外一个比较远,这个按钮回得到自己的constraints。然而,如果你把两个按钮放的足够近,那么这两个按钮的constraint会开始互相作用。

把新的按钮捕捉到原来按钮的旁边:

Snap two buttons

现在这里会出现一些点状guidelines,但Interface Builder不会把他们全部转换成constraint;因为这有一点多了。但这基本会认出这两个按钮能在各个方位对齐-在他们的顶部,中心以及基线处。

在把新按钮放下后,这个按钮的constraints会看上去像这样:

Two buttons

如图所示,新的按钮有一个Vertical Space对于屏幕的底部,也有一个Horizontal Space对于另外一个按钮。但是这个space是非常小的(只有8points),T型状对象可能很难看到,但一定是存在于那里的。

在文档概要图中选中Horizontal Space constraint :

Highlighted H-space between buttons

当你选中一个constraint的时候,它会在屏幕中属于它的地方高亮显示。这个显示在两个按钮之间特别的constraint ,它的意思是在说:

“第二个按钮将会一直出现在第一个按钮的右边,无论第一个按钮的位置以及大小如何变化。”

选中左边的按钮然后输入一些字比如说“A longer label”。你会看到当新的标题输进去以后,左边的按钮重新设置了其尺寸,并且另外一个按钮也移出了它原来的位置。但是它始终是附属在第一个按钮的右边边界,这也就是我们想要的结果:

Button with longer label

请注意Interface Builder重新用一个Horizontal Space代替了原来的Center X Alignment。每次当你对控件做一个尺寸的(或者位置)的改变,Interface Builder会计算出一个它认为对的constraint。通常来说,它都是对的,但有时候它会完全误解我们的意思。在这里,你明显想要将按钮保持在中心位置当你在改变其中的文字时。

把按钮重新放置到它的中心对齐处去。看看现在的constraint是怎么样的:

Two buttons disconnected

这可能不是你想要发生的。现在两个按钮之间不再互相连接了。取而代之的是,新的右按钮和屏幕的右边缘有了一个Horizontal Space的constraint。两个按钮之间没有Horizontal Space了。

当然,你可以通过把捕捉在一起然后再将他们重新连接,但这个问题是可以通过不拖拽view来避免的。

首先,通过快捷键Cmd-Z来取消操作,使第一个按钮不再中心对齐。现在选中按钮并且在Editor 单选择 AlignHorizontal Center in Container这次不仅是第一个按钮移到了屏幕的中心处-另外一个也跟着移了过来。更可能应该这样操作吧!

为了能够对之概念有更好的了解,还是多做一些操作吧。选中小的按钮然后把它放到大的上面去,这样以来他们被捕捉进了place vertically(但不要尝试对齐这两个按钮的左边缘):

Button on top

因为你把两个按钮捕捉在了一起,所以现在他们之间有一个Vertical Space。这个间距依然是由HIG推荐的8 points远。

提示: 这个 “HIG”, 是 iOS Human Interface Guidelines的简称, 介绍了苹果对设计优秀的用户界面推荐设置。对于IOS开发人员来说是必读的.。HIG 解释了在哪种情况下选择哪种UI元素是合适的,并且提供使用他们的最好实例. 你可以在这里查看它。

你可以对于控件之间的标准距离不受限制。constraint是完全成熟的对象,就像view,所以你也可以改变其属性。

选择两个view之间的Vertical Space constraint。你也可以通过点击T型状对象来做到,尽管这有点太过讲究。但目前为止最简单的方式还是点击文档概要图里面的constraint。一旦你选中了,切换到Attributes inspector:

V-space attributes

默认的Standard attribute的勾是选上的。对于两个对象之间的space constraint是8 points;对于一个view于之superview之间的边缘距是20 points。在constraint框里面输入40来改变其constraint大小。看,现在两个按钮离的更远了,但他们之间还是连接着的:

V-space larger

运行程序来看看效果:

V-space larger landscape

按钮之间明显保持着他们的垂直距离安排,但是他们的水平方向却没有!

如果你仔细看nib文件,你会发现上边的按钮和canvas的左边缘之间有一个Horizontal Space(如果你像我一样在同一个点粗糙的放置了那个按钮的话):

H-space anchoring top button

底部的标签在屏幕中水平居中对齐,但上边的按钮不是的-它总是和左边缘保持着一样的距离。

这看上不是很理想,其实你想要的结果是如下目标:

“底部按钮应该一直保持水平居中,并且顶部按钮左边界要一直对齐着底部按钮的左边界。”

对于第一中constraint,你已经拥有了,但是第二种你还没有。Interface Builder 会显示对齐的guides, 所以你能够向左拖拽上边的按钮直到它的边界捕捉到下边的边界:

Snap left edges

不幸的是,这个操作移出了两个按钮之间的Vertical Space(至少在有些时候,这取决于控件是如何拖拽以及放置的操作)。Interface Builder根本“忘了”那曾经有一个Vertical Space,取而代之的操作是于底部的view又重新生成一个Vertical Space:

Two buttons left aligned, wrong V-space

这和你想要的结果差的太远了。结果不应该是在两个按钮之间有一个Vertical Space 吗?而不是窗口的底部产生一个Vertical Space。这个漫画可以表达你的心情当这一切发生时。


第二部分


Learn Auto Layout in the iOS 6 SDK!

来自Ray:恭喜各位!你们已经通过宣传ios feast提前解锁了第一个有关IOS6的教程。

目前这份教程只是我们的新书iOS 6 By Tutorials里面某个章节的精简版。这份教程由同样著作过iOS Apprentice Series的Matthijs Hollemans 完成,开始体验吧!

这份教程由IOS 教程小组的组员Matthijs Hollemans发布,Matthijs既是一位经验丰富的IOS程序员又是一名资深老到的界面设计者。.


在这系列教程的第一部分你见识到了旧的“struts-and-springs” 模式不能简单的解决所有的用户界面布局问题。新的IOS 6特征是一种解决方案,但因为这个技术是如此的有效,它使用起来还是有点小棘手的。在这系列教程的第二部分也是最后一部分, 你将继续学习constraint的概念以及如何运用他们!

大胆尝试constraint

也许你已经注意在canvas里面到有些T型状对象看上去比其他的要粗一点。这些加粗的,我们称之为user constraints,你删除它们后的效果和删除细的是不同的。当你删除一个user constraints,Interface Builder经常会自动在删除的地方放置一个不可删除的constraint来代替之。我马上就会讲到为什么会这样。
在文档概要图中,user constraint有一个蓝色按钮:

User constraints document outline

 

选中 Vertical Space (40) constraint 然后按一下键盘上的删除按钮。 在两个按钮之间的T形状对象消失了,取而代之的是一个新的直接连接屏幕底部的Vertical Space constraint :

After deleting user constraint

新的constraint有一个紫色的按钮,并且图中它的线没有被加粗,这也就代表着它是不可删除的。现在这两个不再在垂直方向连接在一起了。尽管由于Leading Alignment constraint 而依然左边对齐着。
为什么会发生这种事情? 为什么 Interface Builder对按钮发出了一个新的 Vertical Constraint , 甚至你还没有告诉它要删除这么一个constraint?答案是:

对于每一个view都必须要有足够的constraints来对其进行位置以及大小的控制。

当用到 Auto Layout时,这是一条最重要的规则。如果对于一个view没有足够的 constraints, 它的 Auto Layout 将不能决定它的位置以及大小。 这种布局是被认为无效的。待会你会看到几个无效布局的例子。

Interface Builder会尽量帮你避免布局无效。这两个按钮的尺寸是可以知道的因为他们根据他们包含的文本,背景图以及其他的一些东西-固定尺寸内容是可以确定下来他们的尺寸,还记得不?所以这不会是一个问题,上方按钮的X-位置也可以通过它的左边界与下方按钮的左边界对齐来获取,而下方按钮又一直保持着底部居中。现在唯一不确定的就是它的Y-位置。

之前的话,这两个按钮用一个 Vertical Space相连。这个条件足够能推导出上方按钮的Y-位置。但如果你删除了Vertical Space,上方按钮就没有依据来定位它在view里的垂直位置了。因为Auto Layout不知道如何决定它的Y位置,所以它不能在屏幕中显示出来。

为了避免这种情况发生,Interface Builder需要重新在view中找一个离按钮底部边界最近的地方“pin”它。

Pin all the buttons

有趣的是,当你重新运行程序并且转动屏幕至景观方向时,还是之前的效果嘛。这也是正常的,但你的设计确实从根本上就不同了:这两个按钮现在都与窗体的底部相连。这也就意味着,当下方按钮移动时,上方的按钮不会跟着它一起动了。(注意这个解决方案也不算很好,好不好取决于和你理想的效果是否相同。在这个例子中,你明显是希望这两个间有一个vertical connnetion。)

我们证明一下,选中底下那个按钮的和屏幕底边的一个Vertical Space constraint,然后到Attributes inspector ,它的常量现在是“Auto”,目前它是一个标准尺寸,现在把它改为40。
因为这两个按钮选择没有链接,所以现在只有下方的按钮往上面移动了;上方的按钮还是保持原位:

Only bottom button moves up

注意当改变 constraint常量值时会将它提升为一个 加粗的 “user” constraint。

针和销

现在让我们把两个按钮在再次连接在一起。目前为止你通过把按钮拖进canvas来得到了一些constraints,你可以在稍后做这些。按住Cmd键然后同时选中它们,在Editor菜单, 然后选择PinVertical Spacing。

你也可以通过右下角的小面板菜单来做这个constraint:

Shortcut menu pin

它会弹出如下菜单:
Pin menu

 

先不管你选择了哪种方法, 这个操作在两个按钮之间添加了一个新的constraint:

Pin vertical spacing

这个新的constraint是一个有常量为20 points的Vertical Space 。这仅仅是因为当你把两个按钮连接起来的时候,他们之间的距离是20 points。

注意原来从上方按钮到屏幕底部的Vertical Space还是存在的。这个constraint是Vertical Space(104)- 现在已经不需要,所以删了它吧。

之前当你删除一个蓝色constraint的时候,一个紫色会出现并且取代它。现在这种事情不会发生了,因为剩下的constraints已经足够表达清楚所有view的位置。只有在现有constraint不够的情况下,Interface Builder才会添加新的constraint。

现在你的constraints应该如下所示:

Restored constraints

选中底部的 Vertical Space (通过在canvas上点击) 然后把它的constant 40的值改回为标准值。这步不仅仅应该把下方按钮往下移,上方的按钮也该移动。因为现在他们又被连接在一起了。

一个动态运行小测试

现在你知道的一点基础知识有:如何使用guides来放置控件,如何使他们相连对齐,如何在空间之间设置空白空间。在这篇教程后,你也会了解到Align and Pin 菜单的其他选项。

使用Interface Builder 来鼓捣constraints是非常不错的, 但现在让我们来看看,在程序运行的时候这个是如何工作的。在?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里面给这两个按钮连接方法:对每个按钮按住Ctrl拖至File’s Owner 然后在组里面选择buttonTapped:

运行程序看看这会怎么样。并且同时在景观方向和肖像方向设置测试。

Long and short titles

不论你触碰的是哪个按钮,现在的页面布局总是能够符合你提出的contraints:

  • 在水平方向,下方的按钮总是在窗口中水平居中。
  • 下方的按钮总是和窗口的底部保持20 points距离。
  • 上方的按钮总是和下方的保持左边界对齐。

这是你设置的整个用户界面的规范。

你可以为了乐趣,同时在Interface Builder选中两个按钮,然后从Align菜单选择Right Edges。然后运行程序注意观察这其中的区别。

重复地,但现在请选择AlignHorizontal Centers。这会使上方的按钮中心和下方的按钮的中心对齐。现在运行程序看看这些按钮是如何工作的。

修复宽度

Pin 菜单有一个 Widths Equally选项。 如果你的两个view设置了这个constraint,那么Auto Layout 会一直保持两个view的宽度相等,这个宽度值取决于较宽的那个view。让我们来对这个做一个实验。

选中这两个按钮然后在菜单里选择?PinWidths Equally。?这步操作为两个按钮添加了一个新的constraint:

Buttons widths equally

提示: 如果两个按钮的任意一个与superview有一个你不想要的constraint,那么请再次同时选中两个按钮然后执行 AlignHorizontal Centers选项。

在这系列教程的第一部分,你其实是已经见识过了这类constraint。这个看上去和普通的T型状对象类似,但其实在这T型状对象的中间有一个圈,圈里是有一个等于号的。

看,在文档概要图里又多了一个 ?Equal Widths constraint:

Equal widths in document outline

现在改变其中一个按钮的标签内容也会同时改变到另外个按钮的尺寸了。

把下方按钮的标签内容改至 “X”,让它足够小。你可以注意到上方按钮的尺寸不在兼容它的内容了:

Top button text no longer fits

那么Interface Builder是如何知道要选用哪个按钮的尺寸的呢? 如果你足够细心的话,你会发现有一个, Width constraint 被加到了上方按钮:

Width constraint on truncated button in document outline

Width constraint on truncated button

Interface Builder 强制上方按钮变小,为了满足Equal Widths constraint这个限制条件。

很明显这不是你想要的结果,所以请选择上方按钮然后在Editor菜单里为它选择Size to Fit Content(或者按快捷键 Cmd =)。现在,按钮里面的文本又能全部看到了 –  或者更科学的说,这个按钮能够根据包含的文本而调整大小了 – 另外它的Width constraint也消失了。

运行程序然后点击按钮。现在两个按钮一直保持相同宽度了,不论哪个按钮的长度比较宽:

Buttons equal widths in app

当然当两个按钮都变得非常短时,他们还是会一起缩小到相等宽度。除非有个constraint来做限制,不然的话按钮控件是会根据他自己包含的内容来不多不少的调整自己的尺寸到一个恰当的大小。这个叫什么功能呢?对的,这就是固有内容尺寸。

固有内容尺寸

在 Auto Layout功能出现之前, 你通常会设置你的各种控件该有多大,或者在通过定制他们的frame、bounds属性来改变大小,或者直接在Interface Builder来改变他们的大小。 但目前的情况是大部分的控件完全有能力基于它们的内容来计算出自生所要占的空间大小。

一个label能够通过设置在它上面的文本长度以及文本字体来计算出自己的宽和高。类似地,button,可以通过在它带有背景的文本以及一些圆角的填充来计算出适合自己的宽和高。

这也适用于很多分段控件,如一些progress bars,还有许多的其他控件,尽管有一些是有一个预定义的高,但是它宽还是可以设置的。

这就是所谓的 固有尺寸内容, 在Auto Layout这是一个重要的概念。在按钮的操作中你已经见识到了吧。. Auto Layout 会先问你的空间他们有多大,然后在基于空间给出的信息将他们展示到屏幕上去。

你也可以不使用它,但你要明确设置这个空间的Width 或者 Height constraint。如果你这么做了,那么 Interface Builder 为自动生成一个你设置的constraint。如果想要再次恢复固有内容尺寸的话,你只需重新使用Size to Fit Content 操作, 另外之前你设置的 Width or Height constraints会自动消失.

通常来说,你使用固有内容尺寸功能就够了,但有些情况下这功能还是尽如人意的。想象一下,当你需要在UIImageView上设置一个image的时候,如果那个image要比屏幕大的多,你通常会给image设置一个合适的宽度以及高度来适应UIImageView的内容尺寸,除非你想让imageview来自动帮你重新设置image的dimensions。

如果对一个按钮设置一个固定的 Width constraint,情况会怎么样? 虽然按钮能够计算自己的尺寸,但是你能通过给他设置一个固定的尺寸来使它的计算无效。选中上方的按钮然后在菜单选择PinWidth。 看,按钮上方有了一个固定的T型状对象了:

Button fixed width constraint

因为这类的constraint只应用于按钮本身,而不是它的superview,它被列在文档概要图中的按钮对象下方。就像你刚才所做的,现在这个按钮的固定尺寸被设为了73 points。

运行程序然后点击按钮,看看发生了什么?按钮的文本内容改变了,它不再全部显示内容了因为缺少足够多的空间:

Button text clipped

因为上方按钮有个固定的尺寸又因为两个按钮被要求是同一尺寸,所以他们不会再缩小和放大了。

提示: 你通常的设计理念是不会对一个按钮设置Width constraint – 最好的情况是让按钮使用它自己的固有内容尺寸 – 但当你遇到过一个你希望控件自动改变尺寸而它没有改变的布局问题时,那么最好请多次确认一下Interface Builder里面有没有一个固定的 Width constraint。

还是多玩一会这个东西吧以便于你真正掌握 对view对象的pinning 以及aligning 。因为不是所有的现象都很明显,所以你最好对它有一种感觉。但是请你记住,对于必须要有足够多的constraints,  Auto Layout 才能对所有的view判断出位置以及尺寸。

Got enough constraints

图库实例

现在你应该对constraint是什么以及知道如何在不同view之间建立关系来构造你的布局。 接下来,你将会看到如何使用 Auto Layout 以及 constraints 来创造一个符合现实世界场景的布局。

假设你要制作一个关于你最喜欢的程序员的图库应用,在景观和肖像方向,它看上去应该如下图所示:

The Gallery app

这个屏幕现在被分成了4块大小相同的部分。每个部分有个imageview以及一个label。你怎么样来做到这个呢?

让我们重新开始这个程序。你可以使用你现存的“constraints”项目通过把里面的按钮全部删光。

或者,你可以重新创建一个新项目,用 Single View Application 模板然后你随便给他取一个名字你喜欢的名字。比如说,“画廊”。这里面只需要使用nib文件,所以请禁用掉storyboards 选项。

从对象库打开ViewController.xib文件, 把一个普通的view放到canvas上。把它的宽设为160 points,高设为230 points, 然后把背景改为你喜欢的某种颜色 (我把它改为绿色):View with auto layout

这个view在它的空间内有4个constraint。不像一个button或者label,一个普通的UIView 没有固有内容尺寸功能。所以它必须要有足够的constraints来判定它的位置以及大小,所以现在这个view需要constraints来决定它需要的尺寸大小。

你可能想知道,这些尺寸的constraints在哪里?在这种情况下,这些view的尺寸通过他们superview的尺寸来得到。在这个布局中,有两个Horizontal Spaces 以及两个Vertical Spaces,并且这些都有固定尺寸。你可以在文档概要图中看到这些:

Constraints for UIView in document outline

绿色view的宽度可以通过这个公式 “superview的宽度 减去(109 + 51)” 来计算得到,类似的它的高度可以通过 “superview的高度 减去(153 + 77)”。 因为这些空白constraints被固定了,所有view本身没有机会来改变大小。但当你转动应用时,superview的尺寸从320×460变为480×300。把这新的宽度以及高度放到公式中,你会得到一个新的绿色view的尺寸。

你可以通过运行程序并且转动至景观模式看到这个现象,但你也可以直接在 Interface Builder里直接模拟这种情况。

选择nib文件里最顶层的view 然后到 Attributes inspector里去看,在 Simulated Metrics 部分里, 把方向改为景观模式:

Simulated metrics landscape

这里可以看到 nib 文件在景观方向的直接布局现实。这个绿色view为了要满足 Horizontal and Vertical Space constraints 而重新改变了它的尺寸。

再切回到肖像方向。

提示:现在有两个理由解释在nib文件上你为什么要用一个普通的UIView : a) 你将要用它来做其他view的容器,使用它可以帮助你组织你nib文件的内容。; b) 这对于一个定制的view或者control来说是一个占位标志,你可以把它类属性设置为你自己写的UIView或者 UIControl的子类。

有时当设备转动时你不是总希望你的UIView 重新改变大小, 所以现在我们通过 constraints 来定制view的固定宽度/或者高度。我们来动手做一下。 选中绿色view然后在 Pin 菜单, 选择Width。同样地再次选中绿色view然后在菜单里选择 PinHeight。

你现在可以对view添加两个新的constraint,一个160 point的Width constraint 和一个230 point Height constraint:

Width and height constraints on UIView

因为宽度和高度只适用于这个view,在文档概要图中,他们处在view自身的目录下。通常来说,cnostraints只表现为两个view之间的关系 – 比如说,在绿色view和它的灰色superview之间有 Horizontal 和 Vertical Space constraints 这个关系 – 但你也可以认为 Width 和Height constraints 是view和它自身的关系。

现在运行程序。恩!在肖像方向看上去不错。 现在把view翻转至景观方向。擦! 它的样子不仅不是你想要的 – 它自身的尺寸又改变了 – 而且Xcode也报了一个令人讨厌的错误信息:

Gallery[68932:11303] 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) (    "",    "",    "",    "",    "")Will attempt to recover by breaking constraint  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.

是否还记得我说过对于view来说必须要有足够多的constraint,那么Auto Layout才能计算出所有view的位置以及尺寸?那么,现在这个例子的情况是它有太多的constraints了。无论何时当你得到错误 “Unable to simultaneously satisfy constraints”时, 这在暗示你的constraints在某些地方起冲突了。

让我们再来看看那些contraints:

Conflicting constraints

对于这个绿色view,目前有6个constraint。四个之前你见过,另外两个新的 Width 和Height 是你刚加上去的。那么冲突起在哪里呢?

那么, 在肖像模式中数学的计算是没有问题的。superveiw的尺寸是320 points。如果你把绿色view的宽度再加上Horizontal Space的长度,那么你应该得到320这个值。我定位绿色view的方式是:51 + 160 + 109 = 320。类似地,垂直vertical constraints 应该加到 460。

但当你转到设备至景观模式时,主窗口 (也就是它的superview)是 480 points 宽。这意味着 51 + 160 + 109 + ? = 480。 在这等号的左边还需要一个160 points但是Auto Layout却不知道去哪里得到它。类似地,垂直坐标也是这个情况。

现在我们遇到的冲突为以下两种情况中的一种,要么view的宽度是固定的但是它对应的页边必须是可以改变大小,要么它对应的页边是固定的但是它的宽度可以改变大小。 在上面的例子中,你想要这个view在两个方向保持相同的宽度,所以后面的Horizontal Space 必须要删除掉。

把右边的 Horizontal Space删除以及把底部的Vertical Space删除。现在 nib 文件应该看上去这样了:

Conflicting constraints fixed

现在这个view已经有恰当数量的constraint来决定它的尺寸以及位置。不多也不少。再次运行程序,可以看到xcode报错信息已经没有了,这个view也在屏幕转动后保持了相同尺寸。

提示: 尽管 Interface Builder 已经很努力的帮你避免掉无效的布局了,但是它不是万能的。 可是至少Auto Layout会为你报出一个详细的错误信息当你有些设置是错误的时候。 在iOS 6 by Tutorials这本书里的”Intermediate Auto Layout”你可以学到更多关于如何分析错误信息以及如何诊断布局问题的介绍。

把肖像画出来

在绿色view中拖进一个label。 注意现在的guides是在绿色view中出现了,因为它是label的superview。

Drag label into green view

根据guides把label放到绿色view的左上角。这会给label在绿色view中添加两个 Space constraints 来定位自己:

Label constraints

注意这两个新的 Horizontal and Vertical Space constraints 是被列在绿色view的contraints目录下面而不是main view下。

现在稍微把绿色view移动一点。你可以看到只有在绿色view和它的superview之间constraints改变了。而不是label的那些。 这个label会根据绿色view一直待在同一个相同的地方。

选中label然后把它移动到绿色view的底部边缘,并使它中心对称。然后在nib里拖进去一个新的imageview,使布局看上去如下图所示:

Image view in gallery

把这imageview固定在顶部,以及它superview的左右两个边界。但它的底部是通过一个标准spacing与底部标签相连。

下载 这份教程的素材 然后解压文件。在里面你会找到一个 Images 文件夹 – 把它加入到你的项目中去。设置 Ray.png 作为你imageview的image, 然后把 image view的模式改为Aspect Fit并且设置它的背景颜色为白色。接着把label的text写为“ray”。

你的布局现在看上去应该是这样的了:
Gallery with Ray

请注意现在Interface Builder 已经在label上设置一个 Height constraint了。这发生在你为你的image view设置image的时候。

Label fixed height

Interface Builder已经尝试在避免某种含糊的布局的情况发生。如果image view或者label都没有一个固定高度的话,那么Auto Layout是不知道如何重新设定他们的高度的当绿色view变化时。(对于现在而言Interface Builder似乎会忽略设置在绿色view上面的固定Height constraint)。

让我们来假设在某种情况下你app里的绿色view变高了100 points。那么现在这多出来的100 points在imageview和label当中是如何分配的呢?是不是imageview也变高了100 points而label保持原样呢?或者说是label变高了而imageview保持原样?或者说他们是对半分的,25/75分,46开?,或者其他某种分法?

Auto Layout是不会做这些假设的,所有只有Interface Builder来通过给定label一个固定尺寸“帮助”我们解决这问题。当然它也可以给imageview一个固定尺寸,但是给label是更有意义的。

对于此刻来说, 让我们姑且先使用label上面的Height constraint。

提示: 对小布局设计来说一个比较恰当的解决方法是改变label的“Content Compression Resistance Priority”。这点你以后会学到。如果你等不及了想使用的话,那么请直接到label的Size inspector for the label然后设置vertical Content Compression Resistance Priority到751。可以到看到label上的 Height constraint会消失掉的。

 

加入其他头像

把绿色view移动到main view的左上角,是否还记得绿色view的Horizontal Space 以及 Vertical Space constraints 判定了它在superview中的位置。依然还是那些constraints,但是他们现在的值被设为了0 - 蓝色线条的就是他们(有白色边界的) 在window的左上角:

View in top-left corner
虽然这个view是完全在角落里的,但它还是需要constraints来把它定位在那里。就把他们想象成边缘值为0吧。

选中绿色view然后按 Cmd-D 复制。把复制品移到右上角去:

Green view in top-right corner

再分别复制他们把他们移动左下角以及右下角。

把屏幕设计成如下所示:

Gallery design

这几位是比较帅的程序员 :-)

运行程序,可以看到肖像方向,界面看上去还不错,但是在景观方向就不尽如人意了:

Gallery looks bad in landscape

这应该很明显可以看出错在哪里了: 你已经对4个view容器设置了固定的宽和高,所以他们会一直保持这那些尺寸,无论它的superview的尺寸如何变化。

选中4个view里面的 Width (160) and Height (230) constraints 然后删除他们。现在运行程序试试,你会得到下图所示,还是不尽如人意:

Still looks bad in landscape

这个问题看上去有点像我们之前在介绍里面解决过的问题,所以让我们回想一下当时是如何解决的,你应该记得我们是给view设置相同的宽和高来解决的。

选中所有4个view然后做 PinWidths Equally操作。再次重新选中然后再做PinHeights Equally操作。

再次运行程序并且把之转到景观方向。嗯….它还是和之前的一样嘛。这是为什么呢?

好吧,如果你仔细观察上面的截图,你会发现其实每个view确实都有相同的高度,并且他们的宽度似乎也相同(只不过绿色的和棕色被黄色和蓝色部分遮盖住了),所以我们的contraints是对的,是的被满足的。现在的问题仅仅是宽度和高度不是你想要的。如果这是这样,那么肯定还有其他constraints在其中起作用了。

100%肯定,如果你仔细看这些view的constraints,你会看到他们同样的有Horizontal 以及 Vertical Space constraints来强迫他们定位自己的位置。 (请看main view的constraints,而不是4个子view的):

Bad H-space

更加悲剧的是,你都不能删除这些 constraint,main view的T型状对象不是蓝色粗体,所以可以推断出 Interface Builder 把他们放在那里是为了避免某种布局问题的。

它为什么要这么做呢? 目前只能这么解释,所有的4个view都必须拥有相同的尺寸大小是不足以决定他们确切的的尺寸应该是多少的,因为Auto Layout是不知道他们几个是如何连接在一起的。在我们设计上,他们是边和边的连接在一起,但是他们之前却没有这种constraints。所以Auto Layout不会知道它需要把“Ray” and “Matthijs” 块宽度上需要分开。

如果Auto Layout不能通过自身才推断出这层意思的话,那你就只能自己来告诉他了。

To be related

选中 Ray 和 Matthijs 块然后选择 PinHorizontal Spacing操作。因为这些块是边与边相连的,所以在他们之间需要添加一个尺寸为0的Horizontal Space constraint,有了这个, Auto Layout 就知道他们是如何关系的了。

重要提示: Interface Builder不会自动帮你删除黄色view和superview之间的主导Horizontal Space限制(就是上一张截图所示的),但它会将它升级为一个user constraint(比较粗的T状对象)。 你现在可以把它删了。如果你不删的话,你会得到一个“Unable to simultaneously satisfy constraints”的错误当你把应用转至景观方向时。

运行程序,看看现在是什么效果了:

Landscape a bit better

这个看上稍微好一点了。现在四个view是相同宽度,但高度还是不对的。解决方案和之前的类似:在Ray和Dennis Ritchie之间放一个 Vertical Space然后把Dennis的view和window顶部之间的Vertical Space删除。

再次运行程序,现在看上去应该是这样了:

Gallery landscape OK

请注意“Dennis Ritchie” label并不在它imageview的下方正中心。这件事最开始发生在当我想要在label里输入文字的时候。这个label初始化时是放在view的中间的,但Interface Builder还是觉得把这个中心constraint替代为一个Horizontal Space效果会更好。如果你也有这个问题,那么请选中这个label然后进行AlignHorizontal Center in Container操作来修复它。

请再次注意下这些imageview:他们是延伸出来的,因为你没有给他们设置一个固定尺寸。你可能还不知道这一点,但设置他们确实是你的义务。:) 在景观方向下,这些view是不会自动修复的。但是,如果你想要一个imageview保持它原来的比例的话,那你就不是那么走运了。如果你不使用Interface Builder来做一点修改的话,你是不可能得到下图的效果的:

Aspect ratio on images

不幸的是, Interface Builder目前不提供一些能保持view原始比例的constraints。要做那个效果,你需要通过代码自己创建以及设置constraints。在iOS 6 by Tutorials里面有一章”Intermediate Auto Layout”是介绍具体怎么做的。

小贴士:你之前可能已经知道了你可以通过在Simulated Metrics来设置方向以便于你预览用户界面。你也可以直接Interface Builder里面测试各种view的缩放行为。

选中main view,在Simulated Metrics下,把尺寸设置为Freedom。现在你能nib文件添加缩放处理,你可以把它设置成你想要的任何型状。顺便也请放心,Auto Layout会马上重新为你计算出新的布局的:

Freeform

然后,你也要对此警惕。因为有时候当你重新设置尺寸时 Interface Builder 会给它自己添加新的constraints。就像现在这儿的右下角有一个新的Horizontal Space一样。当然这也可能会删除现有的constraints,当你把nib文件放大出它原来的bounds的时候。

接下来去哪?

如果你从头看到这里了,那么恭喜!- 你现在已经知道了关于Auto Layout的所有事情,并且已经对基础知识做了实践!但是在这方面还是有许多需要你去学习…

现在这份教程也就是你刚读的只是 iOS 6 by Tutorials 这本书里 Beginning Auto Layout chapter章节的前半部分。后半部分会教到如何使用 Auto Layout来创建更多 “现实世界”的屏幕布局,以及有关同时使用Auto Layout 和Interface Builder 的所有技巧。

和其他视觉设计工具一样, Interface Builder 有它自己的限制但是有时候如果它和 NSLayoutConstraint对象一起使用的话是会效果更好。 iOS 6教程书对于这个主题投入了一整个的章节- Intermediate Auto Layout.。所以如果你想要对此刨根问底的话, 那就买书看吧!

作者:

 

 

 

这是一篇教程由IOS 教程小组的成员 Matthijs Hollemans, 既是一位经验丰富的IOS程序员又是一名资深老到的界面设计者。


0 0
原创粉丝点击