关闭

IOS6 自动布局 入门(2)

标签: IOS6 自动布局 入门
109人阅读 评论(0) 收藏 举报
分类:

来自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 中加入如下方法:


  1. - (IBAction)buttonTapped:(UIButton *)sender  
  2.   
  3. {  
  4.   
  5.     if ([[sender titleForState:UIControlStateNormal] isEqualToString:@"X"])  
  6.   
  7.         [sender setTitle:@"A very long title for this button"   
  8.   
  9.                 forState:UIControlStateNormal];  
  10.   
  11.     else  
  12.   
  13.         [sender setTitle:@"X" forState:UIControlStateNormal];  
  14.   
  15. }  


这个方法解释为通过触发按钮事件来简单的切换按钮标题的长短。从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<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>[</strong></span><span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;">68932</span>:<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;">11303</span><span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>]</strong></span> Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>in</strong></span> the following list is one you don<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(255, 0, 0);">'t want. Try this: (1) look at each constraint and try to figure out which you don'</span>t expect; <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>(</strong></span><span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;">2</span><span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>)</strong></span> <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(194, 12, 185);"><strong>find</strong></span> the code that added the unwanted constraint or constraints and fix it. <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>(</strong></span>Note: If you<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(255, 0, 0);">'re seeing NSAutoresizingMaskLayoutConstraints that you don'</span>t understand, refer to the documentation <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>for</strong></span> the UIView property translatesAutoresizingMaskIntoConstraints<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>)</strong></span> 
<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>(</strong></span>
    <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(255, 0, 0);">""</span>,
    <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(255, 0, 0);">""</span>,
    <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(255, 0, 0);">""</span>,
    <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(255, 0, 0);">""</span>,
    <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(255, 0, 0);">""</span>
<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(122, 8, 116);"><strong>)</strong></span>
Will attempt to recover by breaking constraint 
 
Break on objc_exception_throw to catch this <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>in</strong></span> the debugger.
The methods <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>in</strong></span> the UIConstraintBasedLayoutDebugging category on UIView listed <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>in</strong></span> <span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>&</strong></span>lt;UIKit<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>/</strong></span>UIView.h<span style="border: 0px; font-family: inherit; font-style: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline;"><strong>&</strong></span>gt; 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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:65747次
    • 积分:1858
    • 等级:
    • 排名:千里之外
    • 原创:92篇
    • 转载:158篇
    • 译文:8篇
    • 评论:2条
    最新评论