目前为止,即使你的界面设计是在合理的复杂度内,你也必须要为之写许多代码来适应变化的布局。现在我相信你会很高兴听到这种情况将不会发生了-对于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:
在 Interface Builder 里点击打开ViewController.xib。在你做任何其他事情之前,请先在nib里把Auto Layout禁用掉。你可以在File inspector里找到这个选项:
取消选择“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中,如图所示:
为了使看起来更清晰,我们把每个view都填注颜色。
现在每个View都离窗体边界 20 points远;各个填充颜色的view之间的距离也是20 points。底部的view是280 points宽,并且顶部两个view都设置成130 points宽。所有的view都设置成200 points 高。
运行程序并且把模拟器或者你的设备旋转至景观方向。你的设备会如下图所以,和我们理想的差距甚远:
提示:你能够通过使用 HardwareRotate Left and Rotate Right的菜单选项来旋转模拟器, 或者通过按住Cmd然后使用向左或者向右方向键来旋转。
而我想要的是让程序运行后是这个样子的在景观方向下:
很明显, autosizing masks 对于要达到这三个view的理想变化还需要做点其他的。 从左上角的View来开始设置autosizing :
这一步使View能紧挨着顶部和左边缘(而不是底部与右边缘),并且在水平和垂直方向上都能够支持伸缩当superview改变其大小时。
类似地, 改变 右上角autosizing 设置:
这是底部view的设置:
运行程序并且转动设备至景观方向。现在应该看上去是这样:
和理想的很接近了,但是还有点瑕疵。三个view之间的距离是不正确的。另外仔细看,这三个view的尺寸也不是100%正确. 造成这个原因是autosizing masks虽然知道要改变view的尺寸当superview改变时,但是它不知道具体该改变多少尺寸。
你可以玩一下 autosizing masks-比如说,改变可以改变的宽和高的值(“springs”)- 但是你几乎不可能精确设置到20-points的距离在三个view之间。
为了解决使用 the springs and struts 方式改变布局所造成的问题,很不幸的,你必须要写一些代码来做。
UIKit 会发送一些消息到你的view controllers当用户界面在开始旋转前,在旋转过程中以及旋转后。你可以通过监听这些消息来改变你用户界面的布局。通常你会重写willAnimateRotationToInterfaceOrientation:duration: 来改变任何需要重新规划的view的frame。
但在你开始做这之前, 你首先需要声明views里面的outlet 属性。
Xcode切换到 the Assistant Editor 模式(在Xcode工具栏的右上角的编辑器工具包的中间一个按钮)然后把每个view拖拉至view controller:
逐个把这些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的位置、尺寸产生冲突。
现在可以运行程序了,然后将设备翻转至景观方向。可以看到每个view的呈现都很理想,再次翻准屏幕至肖像方向,看上去也还不错。
这样做成功了,但是为了这么个简单的呈现你就要必须写许多代码了。想象一下,当你遇到真正更加复杂,特别是动态的那些独立View改变尺寸,或者有一系列的subviews没有被固定时你在代码上所需要作出的努力。
提示: 你还可以使用另外一种方法,那就是同时做好肖像方向以及景观方向的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文件起作用:
提示: Auto Layout 功能在整个nib或者storyboard文件里总是被开启着的。在这两种文件里的所有view都会使用Auto layout功能如果你把勾选上的话。
现在运行程序并且转动屏幕,呈现的样子还是之前的混乱样。
现在让我们启动Auto Layout功能. 按住Cmd键同时选中顶部的两个view (绿色的以及黄色的),。 从 Xcode的Editor 菜单, 选择PinWidths Equally:
再次重新选中这两个view并且做 EditorPinHorizontal Spacing操作。 (即使这两个view看上去像被选中了当你做第一个Pin操作后。但是请注意目前他们处在一种特殊的布局关系显示模式中,你还是必须要重新选中这两个view。)
在左边的文档概要图中, 你会注意到有一个新的section名叫 “Constraints”. 这个section 会被自动加入当你在nib文件中启用Auto Layout时。在这篇文档的下一部分你会了解到这些Contraints是什么以及他们是如何操作的。
现在, 我们把一个名叫 “Horizontal Space (170)” 的从Constraints列表里面删除:
运行程序并转动屏幕. 现在看上去好多了 – 顶部的两个view 有了合适的宽度和间距 – 但还不是我们想要的样子:
按住Cmd键同时选中所有的三个view。 在菜单栏, 做PinHeights Equally 操作。
现在还是按住Cmd键同时选中左上角的以及底部的view,然后做EditorPinVertical Spacing 操作。
最后,把“Vertical Space (240)” 从constraint列表里面删除。
如果你一下子同时选中所有的三个view,Interface Builder应该如下图所示:
蓝色的T型状对象定义了各个view之间的限制。这看上去有点复杂, 但是你一旦学会了,你会发现这种表达相当简洁明了。
运行程序 … 哇, 没有写一行代码每样东西都看上去非常棒了!
酷, 但刚才你究竟做了什么呢?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以及它是如何工作的。
提示: springs-and-struts布局模式也会带来一些其他限制当你从它切换至“Use Autolayout”模式时。对于各个view和屏幕边缘之间的边距都基本会有一条限制,是这么说的:“这个view总是和顶部/底部/左边/右边保持 着20-points的距离。”
你可以看到你的所有contraints在文档概要里。如果你在文档概要里点击一个constraint,Interface Builder会在contraint在view中所体现的地方通过画一条白色的边框并且对之添加一个阴影使其高亮显示:
Constraints是真实的对象 (属于 NSLayoutConstraint类) ,他们也拥有相应的属性。 比如说,选中顶部两个view间距的constraint(名为“Horizontal Space (20)”)然后切换至它的Attributes inspector。 现在你可以通过修改Constant里的值来改变两个view之间的距离大小。
将之设置成100并且运行程序。现在两个view之间的距离更宽了:
当你的程序需要描述各种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设置到下图所示:
你也可以对每个view做autosizing masks设置:
那应该不再是你认为的屏幕设计方式了。使用Auto Layout,你所要做的只是如下图所示:
现在对于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,仅仅是这样哦!
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会显示在屏幕的页边, 也会显示在中心:
如果你之前使用过Interface Builder,那你毫无疑问会对这些guides很熟悉。当你想要对其东西时他们会非常有帮助。而当Auto Layout 启用时,这些guides有了不同的意义。你当然还是需要他们来帮你对其东西,但同时他们也会告诉你新的constraints会体现在哪里。
把button对着guides放到左上角。 现在的nib文件会看上去像这样:
看,有两个蓝色的东西附属在按钮上。 这些 T型状对象就是设置在这个按钮上的constraint。
所有的constraints 也都列在Interface Builder的左边的文档概要面板里:
目前我们有两个constraints, 在button和main view的左边缘之间有一个Horizontal Space, 在button和main view的顶部有一个Vertical Space。这层关系被constraint表示为:
“这个按钮会一直待在它的superview的左上角。”
现在再次选中这个按钮并把它放到nib文件右上角,还是对着蓝色guides:
现在 Horizontal Space 的constraint值改变了。它不再依附在按钮的左边界而是右边界了。
当你对着guides放置一个按钮(或者其他什么view)的时候,你会得到一个标准的大小,这个是被定义在“HIG”里面,这是苹果公司的iOS Human Interface Guidelines文档。对于屏幕的边缘的页边距,标准的大小是20-points的空白。
甚至假设你把button放在某些没有guide的地方,你还是会得到一个Horizontal or Vertical Space的constraint。试一下。然后把按钮像左挪一点,得到如图所示的效果:
目前还是有一个 Horizontal Space的constraint。在文档概要图里,你能看到现在没有一个标准space了。
你的按钮放在哪里,你就会得到一个对应的constraint。
还有一个“center”的constraint.。把按钮拖到canvas的底部中心处,让他正好能卡到中心guides:
请注意这个Horizontal Space constraint现在被一个Center X Alignment constraint取代了,这也就意味着这个按钮会一直跟着它的superview中心对其在水平轴上。依然有一个Vertical Space constraint值保持着这个按钮待在view的最底部。(还是请使用标准页边距)。
运行程序并且转动设备至景观方向。看,甚至在景观方向,这个按钮还是呆在底部的中心位置:
对于这个按钮,这是你想要表达的目的是: “这个按钮应该一直待在底部的中心位置。” 请注意,现在没有任何地方你必须要告诉Interface Builder你的按钮的坐标是什么, 你只要把它放置的view中就行了。
有个Auto Layout,你可以不用在关心你的view在canvas里面的精确坐标位置了。所有这一切,Auto Layout会帮你从你设置的constraints里面派生出来(或者说是Interface Builder为你设置了这一切)。
在这个范例里你可以看出这个按钮在Size inspector里面的转化,那是相当的大啊:
当Auto Layout 禁用时,在X,Y,Width或者Height里的值会改变所选中view的位置以及尺寸。当Auto Layout 启用时,你还是能够在这些框里面输入新的值,但通常结果不会你是想要的效果。那个view 会移动,但是Interface Builder也会基于你的新值来计算出新的constraints。
比如说,把Width的值改到100,canvas里面的按钮会变成下图这个样子:
现在 Center X Alignment constraint的值消失了, 取而代之的是一个把按钮连在屏幕左边缘的Horizontal Space,这个按钮同时也会产生了一个新的constraint,它强制使按钮的宽度固定在100 points(可以看到按钮下方的蓝色栏)。
你在文档概要图的左边看到现在有了一个新的Width constraint:
不像其他的constraint,那些是在按钮和它的superview之间,这个宽度constraint只能应用于按钮本身。你可以认为它是一个按钮和按钮之间的constraint。
拖动按钮使它再次卡在 Center X Alignment constraint 上。
小贴士: 因为通过 Size inspector来改变位置和大小可能会搞乱你的constraints,我建议尽量不要这么做,如果你非要改动布局,请更改constraints。
你现在可能想知道为什么按钮之前没有一个Width constraint。在没有的情况下,Auto Layout是如何知道要改变按钮的长度的呢?
可以这么解释:这个按钮自身知道他的宽度应该是多少,它通过基于它里面的标题文字外加上一些圆角的边距填充,可以计算出来的。如果你设置了一个按钮的背景图片,它也会把这一点计算在内的。
这个现象被认为是固有内容尺寸。不是所有的空间都会这样,但是大部分是这样的(UILable不在内)。如果一个view能够计算出它自己的首选尺寸,那么你就没有必要对其专门设置Width or Height constraints 了。关于这个以后你就看得多了。
为了得到按钮的最佳尺寸,选中它并且在Editor菜单里将至设置为Size to Fit Content 。这步操作会使按钮摆脱明确的Width constraint 并且将之恢复为按钮的固有内容尺寸模式。
两个按钮的探戈
Guides 不仅可以出现在view与superview的, 也可以出现在同一阶层的多个view之间。为了证明这点,现在请在canvas里面拖进一个新的圆角矩形按钮。
如果你把这个按钮放得离另外一个比较远,这个按钮回得到自己的constraints。然而,如果你把两个按钮放的足够近,那么这两个按钮的constraint会开始互相作用。
把新的按钮捕捉到原来按钮的旁边:
现在这里会出现一些点状guidelines,但Interface Builder不会把他们全部转换成constraint;因为这有一点多了。但这基本会认出这两个按钮能在各个方位对齐-在他们的顶部,中心以及基线处。
在把新按钮放下后,这个按钮的constraints会看上去像这样:
如图所示,新的按钮有一个Vertical Space对于屏幕的底部,也有一个Horizontal Space对于另外一个按钮。但是这个space是非常小的(只有8points),T型状对象可能很难看到,但一定是存在于那里的。
在文档概要图中选中Horizontal Space constraint :
当你选中一个constraint的时候,它会在屏幕中属于它的地方高亮显示。这个显示在两个按钮之间特别的constraint ,它的意思是在说:
“第二个按钮将会一直出现在第一个按钮的右边,无论第一个按钮的位置以及大小如何变化。”
选中左边的按钮然后输入一些字比如说“A longer label”。你会看到当新的标题输进去以后,左边的按钮重新设置了其尺寸,并且另外一个按钮也移出了它原来的位置。但是它始终是附属在第一个按钮的右边边界,这也就是我们想要的结果:
请注意Interface Builder重新用一个Horizontal Space代替了原来的Center X Alignment。每次当你对控件做一个尺寸的(或者位置)的改变,Interface Builder会计算出一个它认为对的constraint。通常来说,它都是对的,但有时候它会完全误解我们的意思。在这里,你明显想要将按钮保持在中心位置当你在改变其中的文字时。
把按钮重新放置到它的中心对齐处去。看看现在的constraint是怎么样的:
这可能不是你想要发生的。现在两个按钮之间不再互相连接了。取而代之的是,新的右按钮和屏幕的右边缘有了一个Horizontal Space的constraint。两个按钮之间没有Horizontal Space了。
当然,你可以通过把捕捉在一起然后再将他们重新连接,但这个问题是可以通过不拖拽view来避免的。
首先,通过快捷键Cmd-Z来取消操作,使第一个按钮不再中心对齐。现在选中按钮并且在Editor 菜单选择 AlignHorizontal Center in Container。这次不仅是第一个按钮移到了屏幕的中心处-另外一个也跟着移了过来。更可能应该这样操作吧!
为了能够对之概念有更好的了解,还是多做一些操作吧。选中小的按钮然后把它放到大的上面去,这样以来他们被捕捉进了place vertically(但不要尝试对齐这两个按钮的左边缘):
因为你把两个按钮捕捉在了一起,所以现在他们之间有一个Vertical Space。这个间距依然是由HIG推荐的8 points远。
提示: 这个 “HIG”, 是 iOS Human Interface Guidelines的简称, 介绍了苹果对设计优秀的用户界面推荐设置。对于IOS开发人员来说是必读的.。HIG 解释了在哪种情况下选择哪种UI元素是合适的,并且提供使用他们的最好实例. 你可以在这里查看它。
你可以对于控件之间的标准距离不受限制。constraint是完全成熟的对象,就像view,所以你也可以改变其属性。
选择两个view之间的Vertical Space constraint。你也可以通过点击T型状对象来做到,尽管这有点太过讲究。但目前为止最简单的方式还是点击文档概要图里面的constraint。一旦你选中了,切换到Attributes inspector:
默认的Standard attribute的勾是选上的。对于两个对象之间的space constraint是8 points;对于一个view于之superview之间的边缘距是20 points。在constraint框里面输入40来改变其constraint大小。看,现在两个按钮离的更远了,但他们之间还是连接着的:
运行程序来看看效果:
按钮之间明显保持着他们的垂直距离安排,但是他们的水平方向却没有!
如果你仔细看nib文件,你会发现上边的按钮和canvas的左边缘之间有一个Horizontal Space(如果你像我一样在同一个点粗糙的放置了那个按钮的话):
底部的标签在屏幕中水平居中对齐,但上边的按钮不是的-它总是和左边缘保持着一样的距离。
这看上不是很理想,其实你想要的结果是如下目标:
“底部按钮应该一直保持水平居中,并且顶部按钮左边界要一直对齐着底部按钮的左边界。”
对于第一中constraint,你已经拥有了,但是第二种你还没有。Interface Builder 会显示对齐的guides, 所以你能够向左拖拽上边的按钮直到它的边界捕捉到下边的边界:
不幸的是,这个操作移出了两个按钮之间的Vertical Space(至少在有些时候,这取决于控件是如何拖拽以及放置的操作)。Interface Builder根本“忘了”那曾经有一个Vertical Space,取而代之的操作是于底部的view又重新生成一个Vertical Space:
这和你想要的结果差的太远了。结果不应该是在两个按钮之间有一个Vertical Space 吗?而不是窗口的底部产生一个Vertical Space。