自动布局指南-Part 2:自动布局细则手册

翻译自“Auto Layout Guide”。

2 自动布局细则手册

2.1 堆栈视图

接下来的章节展示了如何使用堆栈视图创建更复杂的布局。堆栈视图是一个强大的工具,可以快速的,容易的设计用户界面。它们的属性允许深度控制如何布局视图。可以使用额外的,自定义的约束进一步设置;但这会增加布局的复杂性。

查看Auto Layout Cookbook项目,获得本章的源码。

2.1.1 简单堆栈视图

本节使用单个垂直堆栈视图布局一个标签,一个图片视图和一个按钮。

2.1.1.1 视图和约束

在界面生成器中,拖拽一个垂直堆栈视图,并添加标签,图片视图和编辑按钮。然后如下设置约束。

  1. Stack View.Leading = Superview.LeadingMargin
  2. Stack View.Trailing = Superview.TrailingMargin
  3. Stack View.Top = Top Layout Guide.Bottom + Standard
  4. Bottom Layout Guide.Top = Stack View.Bottom + Standard

2.1.1.2 属性

在属性检查器中,设置如下堆栈视图的属性:

Stack Axis Alignment Distribution Spacing
Stack View Vertical Fill Fill 8

接下来,在图片视图上设置如下属性:

View Attribution Value
Image View Image 一张花的图片
Image View Mode Aspect Fit

最后,在属性检查器中,设置图片视图的content-hugging和compression-resistance(CHCR)属性。

Name Horizontal hugging Vertical hugging Horizontal resistance Vertical resistance
Image View 250 249 750 749

2.1.1.3 讨论

必须固定堆栈视图到父视图,但堆栈视图没有使用其它任何显式约束,来管理整个布局。

这一节中,图片视图用一个很小的,标准的页边留白填充它的父视图。排列的视图调整大小填充堆栈视图的bounds。水平方向上,每个视图拉伸匹配堆栈视图的宽度。垂直方向上,视图基于CHCR优先级拉伸。图片视图总是缩小和放大填充可用空间。因此,它的垂直content hugging和compression resistance优先级必须比标签和按钮的默认优先级低。

最后,设置图片视图的mode为Aspect Fit。该设置强制图片视图在保持它的长宽比时,调整大小填充图片视图的bounds。这样堆栈视图可以随意调整图片视图的大小,并且图片不会变形。

更多关于固定视图填充父视图的信息,请参考“Attributes”和“Adaptive Single View”。

2.1.2 嵌套堆栈视图

本节展示了一个由多个嵌套堆栈视图构建的复杂布局。但是,这个例子不能只用堆栈视图创建。而是需要额外的约束更一步调整布局。

创建视图层级结构后,添加下一节展示的约束。

2.1.2.1 视图和约束

使用嵌套堆栈视图时,最容易的方式是从里到外。在界面生成器中,从name行开始布局。放置标签和文本框到正确的相对位置,选中它们,然后点击Editor > Embed In > Stack View菜单项。这就创建了一个水平堆栈布局。

接着,垂直放置这些行,选中它们,然后再次点击Editor > Embed In > Stack View菜单项。这会创建行的垂直堆栈视图。如下图继续创建界面。(原文中是水平放置这些行,是文档写错了,还是我理解有误?)

  1. Root Stack View.Leading = Superview.LeadingMargin
  2. Root Stack View.Trailing = Superview.TrailingMargin
  3. Root Stack View.Top = Top Layout Guide.Bottom + 20.0
  4. Bottom Layout Guide.Top = Root Stack View.Bottom + 20.0
  5. Image View.Height = Image View.Width
  6. First Name Text Field.Width = Middle Name Text Field.Width
  7. First Name Text Field.Width = Last Name Text Field.Width

2.1.2.2 属性

每个堆栈视图都有自己的属性集,它们定义了堆栈视图如何布局它的内容。在属性检查器中,设置以下属性:

Stack Axis Alignment Distribution Spacing
First Name Horizontal First Baseline Fill 8
Middle Name Horizontal First Baseline Fill 8
Last Name Horizontal First Baseline Fill 8
Name Rows Vertical Fill Fill 8
Upper Horizontal Fill Fill 8
Button Horizontal First Baseline Fill Equally 8
Root Vertical Fill Fill 8

另外,给文本视图一个浅灰色的背景色。当方向改变时,可以更容易查看文本视图如何调整大小。

View Attribute Value
Text View Background Light Gray Color

最后,CHCR优先级定义了哪个视图应该拉伸填充可用空间。在尺寸检查器中,设置以下CHCR优先级:

Name Horizontal hugging Vertical hugging Horizontal resistance Vertical resistance
Image View 250 250 48 48
Text View 250 249 250 250
First, Middle, and Last Name Labels 251 251 750 750
First, Middle, and Last Name Text Fields 48 250 749 750

2.1.2.3 讨论

本节中,多个堆栈视图共同管理大部分布局。但是,它们自己不能创建所有期望的行为。例如,图片视图调整大小后,图片应该保持长宽比。不幸的是,这里不能使用“简单堆栈视图”中的方法。布局需要同时填充图片的结尾和底部边缘,并且使用Aspect Fit模式会在其中一个维度添加额外的空白区域。幸运的是,这个例子中,图片都是正方形,所有可以让图片完全填充图片视图的bounds,并约束图片视图的长宽比为1:1。

提示
长宽比约束是视图的高度和宽度之间的一个简单约束。界面生成器有多种方式显示该约束的乘数。通常显示为一个比例。所以,View.Width = View.Height约束可能显示为1:1的长宽比。

另外,所有文本框的宽度应该相等。不幸的是,它们在不同的堆栈视图中,所以堆栈视图不能控制。所以必须显式的指定相等宽度的约束。

与简单堆栈视图类似,还必须修改一些CHCR优先级。它们定义了父类的bounds改变时,视图如何缩小和放大。

在垂直方向上,希望文本视图扩展填充上面堆栈视图和下面按钮视图之间的空间。所以,文本视图的垂直content hugging必须比其它垂直content hugging优先级小。

在水平方向上,标签的大小应该是固有内容尺寸,而文本框调整尺寸填充剩下的空间。标签的默认CHCR优先级就可以。界面生成器已经设置content hugging为251,它比文本框的高;但还需要降低文本框的水平content hugging和水平horizontal compression resistance。

图片视图应该缩小,与包含名字行的堆栈视图的高度相同。但是,堆栈视图只是简单地紧靠它们的内容。这意味着图片视图的垂直compression resistance必须很低,才能让图片视图缩小,而不是堆栈视图扩大。另外,图片视图的长宽比约束让布局变得复杂,因为它允许垂直和水平约束相互影响。这意味着文本框的水平content hugging也必须很低,否则他们会阻止图片视图缩小。这两种情况下,设置优先级为48或更低。

2.1.3 动态的堆栈视图

本节描述运行时在堆栈视图中动态添加和移除项。堆栈视图的所有变化都是动画的。另外,堆栈视图位于滚动视图之内,如果列表超出屏幕,可以滚动列表。

提示
本节只是为了描述动态使用堆栈视图,和在滚动视图内使用堆栈视图。在真实的应用程序中,应该使用UITableView。通常,不应该使用动态堆栈视图从零开始实现表格视图的功能。而是使用它们创建动态的用户界面,并且该界面使用其它技术不能容易的构建。

2.1.3.1 视图和约束

最初的用户界面很简单。在场景中放置一个滚动视图,让它填充整个场景。然后,在滚动视图内放置一个堆栈视图,并在堆栈视图内放置一个Add Item按钮。接着如下设置约束:

  1. Scroll View.Leading = Superview.LeadingMargin
  2. Scroll View.Trailing = Superview.TrailingMargin
  3. Scroll View.Top = Superview.TopMargin
  4. Bottom Layout Guide.Top = Scroll View.Bottom + 20.0
  5. Stack View.Leading = Scroll View.Leading
  6. Stack View.Trailing = Scroll View.Trailing
  7. Stack View.Top = Scroll View.Top
  8. Stack View.Bottom = Scroll View.Bottom
  9. Stack View.Width = Scroll View.Width

2.1.3.2 属性

在属性检查器中,设置以下堆栈视图属性:

Stack Axis Alignment Distribution Spacing
Stack View Vertical Fill Equal Spacing 0

2.1.3.3 代码

本节需要一些代码在堆栈视图中添加和移除项。为场景创建一个自定义视图控制器,使用outlet连接滚动视图和堆栈视图。

class DynamicStackViewController: UIViewController {
    
    @IBOutlet weak private var scrollView: UIScrollView!
    @IBOutlet weak private var stackView: UIStackView!
    
    // Method implementations will go here...
    
}

接着,覆写viewDidLoad方法,设置滚动视图的初始位置,让滚动视图的内容从状态栏下面开始。

override func viewDidLoad() {
    super.viewDidLoad()
    
    // setup scrollview
    let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
    scrollView.contentInset = insets
    scrollView.scrollIndicatorInsets = insets
    
}

现在,为Add Item按钮添加动作方法。

// MARK: Action Methods
 
@IBAction func addEntry(sender: AnyObject) {
    
    let stack = stackView
    let index = stack.arrangedSubviews.count - 1
    let addView = stack.arrangedSubviews[index]
    
    let scroll = scrollView
    let offset = CGPoint(x: scroll.contentOffset.x,
                         y: scroll.contentOffset.y + addView.frame.size.height)
    
    let newView = createEntry()
    newView.hidden = true
    stack.insertArrangedSubview(newView, atIndex: index)
    
    UIView.animateWithDuration(0.25) { () -> Void in
        newView.hidden = false
        scroll.contentOffset = offset
    }
}

该方法为滚动视图计算新的偏移量,然后创建新的条目视图。条目视图是隐藏的,并添加到堆栈中。隐藏的视图不影响外观或堆栈的布局,所有堆栈的外观没有变化。然后在动画块中,显示视图和更新滚动偏移量,动画的改变视图的外观。

添加类似的方法删除条目;与addEntry方法不同,该方法没有在界面生成器中链接任何控件。视图创建时,应用程序通过代码链接每一个条目视图到该方法。

func deleteStackView(sender: UIButton) {
    if let view = sender.superview {
        UIView.animateWithDuration(0.25, animations: { () -> Void in
            view.hidden = true
            }, completion: { (success) -> Void in
                view.removeFromSuperview()
        })
    }
}

该方法在动画块中隐藏视图。动画完成后,从视图层级结构中移除视图。这会自动从堆栈视图的视图列表中移除视图。

条目视图可以是任何视图,该例子中使用一个堆栈视图,其中包括一个日期标签,一个随机的十六进制字符串,和一个删除按钮。

// MARK: - Private Methods
private func createEntry() -> UIView {
    let date = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .NoStyle)
    let number = "\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())"
    
    let stack = UIStackView()
    stack.axis = .Horizontal
    stack.alignment = .FirstBaseline
    stack.distribution = .Fill
    stack.spacing = 8
    
    let dateLabel = UILabel()
    dateLabel.text = date
    dateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
    
    let numberLabel = UILabel()
    numberLabel.text = number
    numberLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
    
    let deleteButton = UIButton(type: .RoundedRect)
    deleteButton.setTitle("Delete", forState: .Normal)
    deleteButton.addTarget(self, action: "deleteStackView:", forControlEvents: .TouchUpInside)
    
    stack.addArrangedSubview(dateLabel)
    stack.addArrangedSubview(numberLabel)
    stack.addArrangedSubview(deleteButton)
    
    return stack
}
 
private func randomHexQuad() -> String {
    return NSString(format: "%X%X%X%X",
                    arc4random() % 16,
                    arc4random() % 16,
                    arc4random() % 16,
                    arc4random() % 16
        ) as String
}
}

2.1.3.4 讨论

运行时,可以在堆栈视图中添加或删除视图。堆栈视图的布局自动调整,匹配它的视图数组。有一些重要点需要记住:

  • 隐藏的视图仍然在堆栈的视图数组中。它们不在显示,并且对其它视图的布局没有影响。
  • 添加到堆栈视图数组的视图,会自动添加到视图层级结构中。
  • 从堆栈的视图数组中移除的视图,不会自动从视图层级结构中移除;但是从视图层级结构中移除的视图,会从视图数组中移除。
  • 在iOS中,视图的hidden属性通常没有动画。但是,当视图放置在堆栈的视图数组中时,该属性变成可动画的。堆栈管理实际的动画,而不是视图。使用hidden属性动画的从堆栈中添加或移除视图。

本节还介绍了在滚动视图中使用自动布局。这里,堆栈和滚动视图之间的约束设置了滚动视图的内容区域。宽度相等的约束显示的设置了堆栈(以及内容尺寸)水平填充滚动视图。在垂直方向上,内容尺寸基于堆栈的合适尺寸。用户点击更多条目时,堆栈视图变得更长。只要屏幕上有太多的内容,会自动启用滚动。

更多信息请参考“Working with Scroll Views”。

2.2 简单约束

接下来的章节描述使用相对简单的约束集创建通用的行为。使用这些例子作为创建更大,更复杂布局的基础。

查看Auto Layout Cookbook项目,获的本章的源码。

2.2.1 简单的单个视图

本节中,放置到单个红色视图填充它的父视图,四个边缘有固定的页边留白。

2.2.1.1 视图和约束

在界面生成器中拖拽一个视图到场景中,大小为填充场景。使用界面生成器的向导选择相对父视图边缘的正确位置。

提示:不用担心视图的精确位置。设置约束后,系统会计算正确的尺寸和位置。

放置之后,设置以下约束:

  1. Red View.Leading = Superview.LeadingMargin
  2. Red View.Trailing = Superview.TrailingMargin
  3. Red View.Top = Top Layout Guide.Bottom + 20.0
  4. Bottom Layout Guide.Top = Red View.Bottom + 20.0

2.2.1.2 属性

在属性检查器中设置以下属性,指定视图的背景色为红色。

View Attribute Value
Red View Background Red

2.2.1.3 讨论

本节中的约束保持红色视图跟视图视图的边缘有固定的距离。对于开头和结尾边缘,固定视图到父视图的页边留白。对于顶部和底部,固定视图到顶部和底部布局向导。

提示:系统自动设置根视图的页边留白,所以有一个合适的开头和结尾页边留白(16或20个点,根据不同的设备)和0个点的顶部和底部页边留白。这让你很容易在任何控制栏(状态栏,导航栏,标签栏,工具栏等)下面(under)控制内容。

然而,本节需要在栏(如果有的话)之下(below)放置内容。可以简单的固定红色视图的开头和结尾边缘到视图视图的开头和结尾页边留白;必须相对于布局向导设置自己的顶部和底部页边留白。

默认情况下,视图和它父视图的边缘之间标准间隔是20个点,兄弟视图之间是8个点。这意味着红色视图的顶部和状态栏的底部之间应该使用8个点。但是,当iPhone是横屏时,状态栏会消失。没有状态栏后,8个点有点太窄了。

总是选择最适合你的应用程序的布局。本节对顶部和底部都使用20个点的页边留白。这让约束逻辑尽可能简单,并且在所有方向看起来都很合理。其它布局也许固定8个点的页边留白更好。

如果想要布局自动适应栏的显示和消失,请参考“自适应的单个视图”。

2.2.2 自适应的单个视图

本节放置单个的蓝色视图填充父视图,并且四个边缘带页边留白。不像“简单的单个视图”一节中的页边留白,本节的顶部页边留白根据视图的上下文自适应。如果有状态栏,视图在状态栏下面的标准间隔(8个点)。如果没有状态栏,视图在父视图边缘下面的20个点。

以下是并排显示简单和自适应视图的效果。

2.2.2.1 视图和约束

在界面生成器拖拽一个视图到场景中,调整大小填充场景,边缘对齐到向导。然后设置如下约束。

  1. Blue View.Leading = Superview.LeadingMargin
  2. Blue View.Trailing = Superview.TrailingMargin
  3. Blue View.Top = Top Layout Guide.Bottom + Standard (Priority 750)
  4. Blue View.Top >= Superview.Top + 20.0
  5. Bottom Layout Guide.Top = Blue View.Bottom + Standard (Priority 750)
  6. Superview.Bottom >= Blue View.Bottom + 20.0

2.2.2.2 属性

在属性检查器中设置如下属性,设置视图的背景为蓝色。

View Attribute Value
Blue View Background Blue

2.2.2.3 讨论

本节创建了蓝色视图的顶部和底部都自适应的页边留白。如果有栏,视图边缘离栏8个点。如果没有,视图边缘离父视图边缘20个点。

本节使用布局向导正确放置它的内容。系统根据任何栏的存在和尺寸设置这些向导的位置。顶部布局向导沿着任何顶部栏的底部边缘放置(例如,状态栏和导航栏)。底部布局向导沿着任何顶部栏的顶部边缘放置(例如标签栏)。如果没有栏,系统沿着父视图的相应边缘放置布局向导。

本节使用一对约束构建自适应行为。第一个大于等于约束是必须的。该约束保证蓝色视图的边缘总是离父视图边缘最少20个点。实际上,它定义了一个最小20个点的页边留白。

接着,一个可选约束尝试放置视图离相应的布局向导8个点。因为这个约束是可选的,如果系统不能满足这个约束,它仍会尝试尽可能靠近,并且该约束像一个弹簧,强制把蓝色视图边缘拉向它的布局向导。

如果系统不显示栏,布局向导就等于父视图的边缘。蓝色视图边缘里视图边缘不能同时是8和20个点(或者更多)。因此,系统不能满足可选的约束。系统还是尝试尽量靠近——设置页边留白为最小值20。

如果栏存在,两个约束都能满足。所有栏的宽度至少是20个点。因此,如果系统放置蓝色视图边缘离栏边缘8个点,它会保证离父视图边缘大于20个点。

使用一对约束作为反方向强制推力的方法,通常用来创建自适应布局。在“带固有内容尺寸的视图”的content-hugging和compression-resistance(CHCR)会再次看到这种方法。

2.2.3 两个宽度相等的视图

本节并排布局两个视图。不管父视图的bounds如何变化,视图的宽度总是相同。同时,它们填充父视图,并且所有边有固定的页边留白,它们之间有标准的页边留白。

2.2.3.1 视图和约束

在界面生成器中,拖拽两个视图,让它们填充厂家,使用向导设置对象之间的正确间隔。

不用担心让两个视图的宽度相等。相对位置差不多就可以,让约束完成困哪的工作。

放置视图后,设置以下约束。

  1. Yellow View.Leading = Superview.LeadingMargin
  2. Green View.Leading = Yellow View.Trailing + Standard
  3. Green View.Trailing = Superview.TrailingMargin
  4. Yellow View.Top = Top Layout Guide.Bottom + 20.0
  5. Green View.Top = Top Layout Guide.Bottom + 20.0
  6. Bottom Layout Guide.Top = Yellow View.Bottom + 20.0
  7. Bottom Layout Guide.Top = Green View.Bottom + 20.0
  8. Yellow View.Width = Green View.Width

2.2.3.2 属性

在属性检查器中设置视图的背景色。

View Attribute Value
Yellow View Background Yellow
Green View Background Green

2.2.3.3 讨论

布局明确的定义了两个视图的顶部和底部页边留白。只要这些页边留白相等,视图的高度会隐式的相同。然而这不是唯一可能的解决方案。可以设置绿色视图的顶部和底部与黄色视图的顶部和底部相等,而不是固定绿色视图的顶部和底部到父视图。对齐顶部和顶部边缘,明确的指定了视图有相同的垂直布局。

一个如此相对简单的布局都有很多种不同的解决方法。有些可能更清晰,但它们大致是等价的。每种方法都有自己的优点和缺点。本节的方法有两个主要的优点。第一(也是最重要的),它很容易理解。第二,如果移除其中一个视图,布局几乎保存不变。

从视图层级结构中移除一个视图,同时也会移除该视图的所有约束。这意味着,如果移除黄色视图,会移除约束1,2,4,6,8。但是,还有三个约束固定绿色视图。只需要简单的添加一个约束,定义绿色视图的开头边缘位置,布局就固定了。

主要的缺点是需要手工保证所有顶部和底部约束相等。修改其中一个,视图会变得参差不齐。实际中,使用界面生成器的Pin工具相对容易设置一致的约束常量。如果使用拖拽创建约束,会更困难一些。

当有多个同等有效的约束集时,选择最容易理解和最容易维护布局上下文的方案。例如,居中对齐多个不同尺寸的视图时,约束视图的Center X属性是最容易的。对于其它布局,可能更容易使用视图的边缘,或者高度和宽度。

为布局选择最佳约束集的更多信息,请参考“创建没有歧义的,可满足的布局”。

2.2.4 两个不同宽度的视图

本节与“两个宽度相等的视图”很像,只有一个显著地区别。这一节中,橙色视图的宽度总是紫色视图宽度的两倍。

2.2.4.1 视图和约束

跟之前一样,拖拽两个视图,大致放在正确的位置。然后设置以下约束。

  1. Purple View.Leading = Superview.LeadingMargin
  2. Orange View.Leading = Purple View.Trailing + Standard
  3. Orange View.Trailing = Superview.TrailingMargin
  4. Purple View.Top = Top Layout Guide.Bottom + 20.0
  5. Orange View.Top = Top Layout Guide.Bottom + 20.0
  6. Bottom Layout Guide.Top = Purple View.Bottom + 20.0
  7. Bottom Layout Guide.Top = Orange View.Bottom + 20.0
  8. Orange View.Width = 2.0 x Purple View.Width

2.2.4.2 属性

在属性检查器中设置视图的背景色。

View Attribute Value
Purple View Background Purple
Orange View Background Orange

2.2.4.3 讨论

本节在宽度约束上使用了乘数。乘数只能在约束视图的高度或宽度中使用。它让你设置两个不同视图的相对尺寸。另外,可以在视图自身的高度和宽度设置约束,指定视图的长宽比。

界面生成器可以使用不同的数字格式指定乘数。可以写小数(2.0),百分比(200%),分数(2/1),或者比例(2:1)。

2.2.5 两个复杂宽度的视图

本节几乎与“两个不同宽度的视图”完全一样;但是,这里使用一对约束定义视图宽度更复杂的行为。在本节中,视图尝试让红色视图的宽度是蓝色视图宽度的两倍,但是蓝色视图的最小宽度是150个点。所以,在竖屏iPhone中,两个视图的宽度几乎相等;在横屏中,两个视图更大,但红色视图的宽度是蓝色视图宽度的两倍。

2.2.5.1 视图和约束

在画布上放置视图,然后设置以下约束。

  1. Blue View.Leading = Superview.LeadingMargin
  2. Red View.Leading = Blue View.Trailing + Standard
  3. Red View.Trailing = Superview.TrailingMargin
  4. Blue View.Top = Top Layout Guide.Bottom + 20.0
  5. Red View.Top = Top Layout Guide.Bottom + 20.0
  6. Bottom Layout Guide.Top = Blue View.Bottom + 20.0
  7. Bottom Layout Guide.Top = Red View.Bottom + 20.0
  8. Red View.Width = 2.0 x Blue View.Width (Priority 750)
  9. Blue View.Width >= 150.0

2.2.5.2 属性

在属性检查器中设置视图的背景色。

View Attribute Value
Blue View Background Blue
Red View Background Red

2.2.5.3 讨论

本节使用一对约束控制视图的宽度。可选的比例宽度约束拉拽视图,因此红色视图的宽度是蓝色视图宽度的两倍。但是,必须的大于等于约束定义了蓝色视图的最小宽度。

实际上,如果父视图的开头和结尾页边留白之间的距离是458个点或更大(150.0+300.0+8.0),那么红色视图是蓝色视图的两倍宽。如果页边留白的距离更小,那么蓝色视图的宽度为150个点,红色视图填充剩下的空间(视图之间的页边留白是8个点)。

你可能已经注意到了,该模式的另一个变种在“自适应的单个视图”中介绍过。

可以通过添加额外的约束扩展这种设计——例如,使用三个约束。一个必须的约束设置红色视图的最小宽度。一个高优先级的可选约束设置蓝色视图的最小宽度,一个低优先级的可选约束设置两个视图之间的首选尺寸比例。

2.3 带固有内容尺寸的视图

接下来的章节阐述使用带有固定内容尺寸的视图。通常情况下,固定内存尺寸简化了布局,减少了约束的数量。但是,使用固有内容尺寸经常需要设置视图的content-hugging和compression-resistance(CHCR)优先级,这会增加额外的复杂性。

在“Auto Layout Cookbook”项目中查看本节的源码。

2.3.1 简单的标签和文本框

本节阐述布局一对简单的标签和文本框。这个例子中,标签的宽度基于它text属性的尺寸,而文本框扩展和缩小来匹配剩下的空间。

因为本节使用视图的固有内容尺寸,所以只需要五个约束来唯一确定布局。但是必须确保使用正确的CHCR优先级,设置正确的调整尺寸行为。

关于固有内容尺寸和CHCR优先级,请参考“固有内容尺寸”。

2.3.1.1 视图和约束

在界面生成器,拖拽一个标签和一个文本框。设置标签的文本和文本框的占位符,然后如下设置约束。

  1. Name Label.Leading = Superview.LeadingMargin
  2. Name Text Field.Trailing = Superview.TrailingMargin
  3. Name Text Field.Leading = Name Label.Trailing + Standard
  4. Name Text Field.Top = Top Layout Guide.Bottom + 20.0
  5. Name label.Baseline = Name Text Field.Baseline

2.3.1.2 属性

想让文本框拉伸填充可用空间,它的content hugging必须比标签的低。默认情况下,界面生成器设置标签的content hugging为251,文本框的为250.可用在尺寸检查器中确认。

Name Horizontal hugging Vertical hugging Horizontal resistance Vertical resistance
Name Label 251 251 750 750
Name Text Field 250 250 750 750

2.3.1.3 讨论

请注意,布局只是用两个约束(4和5)定义垂直布局,三个约束(1,2,3)定义水平布局。"创建没有歧义的,可满足的布局"中的经验法则声明,每个视图需要两个水平约束和两个垂直约束;但是布局和文本框的固有内容尺寸提供了它们的高度和标签的宽度,所以不需要这三个约束。

该布局还做了一个简化的鉴定,文本框总是比标签文本高,并使用文本框的高度定义到顶部布局向导的距离,本节使用文本的基线对齐它们。

在水平方向上,仍然需要定义哪个视图应该扩展来填充可用空间。通过修改视图的CHCR完成这项工作。在这个例子中,界面生成器已经设置了name标签的水平和垂直hugging优先级为251。因为它比文本框的默认值250大,所以文本框会扩展填充剩余空间。

提示:
如果布局可能在一个对于控件来说足够小的空间中显示,则还需要修改compression resistance值。该值定义了没有足够空间时,哪个视图应该被缩短。
这个例子中,修改compression resistance留给读者作为一个练习。如果name标签的文本或字体足够大;然而没有足够的空间产生没有歧义的布局。那么系统会选择打破一个约束,文本框或者标签被缩短了。
理想情况下,你希望创建相对可用空间永远不会太大的布局——根据需要,为紧凑尺寸类使用一个替换布局。然而,当设计支持多语言和动态类型(dynamic type)的视图时,很难准确预料行的大小。以防万一,修改compression resistance是一个很好的安全阀。

2.3.2 动态高度的标签和文本框

“简单的标签和文本框”假设文本框总是比name标签高,来简化布局。但是这不总是正确。如果增大足够的标签字体尺寸,它会比文本框高。

本节根据运行时最高的控件动态设置空间的垂直间隔。使用常规系统字体是,本节的结果与“简单的标签和文本框”相同(查看屏幕截图)。但是,如果增加标签的字体尺寸到36.0个点,那么布局的垂直空间从标签的顶部计算。

这是一个多少有点不自然的例子。毕竟,如果增加了标签的字体尺寸,通常也会增加文本框的字体尺寸。但是,在iPhone的辅助设置中设置加大,加大,加大号可用字体,当混用动态类型和固定尺寸控件(比如图片)时,该方法很有用。

2.3.2.1 视图和约束

如“简单的标签和文本框”中一样设置视图层级结构,但是使用多少有点复杂的约束集:

  1. Name Label.Leading = Superview.LeadingMargin
  2. Name Text Field.Trailing = Superview.TrailingMargin
  3. Name Text Field.Leading = Name Label.Trailing + Standard
  4. Name Label.Top >= Top Layout Guide.Bottom + 20.0
  5. Name Label.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
  6. Name Text Field.Top >= Top Layout Guide.Bottom + 20.0
  7. Name Text Field.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
  8. Name label.Baseline = Name Text Field.Baseline

2.3.2.2 属性

想让文本框拉伸填充可用空间,它的content hugging必须比标签的低。默认情况下,界面生成器设置标签的content hugging为251,文本框为250。可以在尺寸检查器中确定这一点。

Name Horizontal hugging Vertical hugging Horizontal resistance Vertical resistance
Name Label 251 251 750 750
Name Text Field 250 250 750 750

2.3.2.3 讨论

本节为每个控件使用一对约束。一个必需的,大于等于约束定义控件和布局向导之间的最小距离,而可选约束尝试拉拽控件到布局向导的距离为精确的20.0个点。

对于更高的约束,两个约束都是可满足的,所以系统精确放置在布局向导的20.0个点的位置。然而,对于更短的控件,只有最小距离可满足。另一个约束被忽略。所以运行时改变控件的高度时,自动布局系统会动态的重新计算布局。

提示:确保设置可选约束的优先级比默认content hugging约束(250)低。否则,系统会打破content hugging约束,并拉伸视图,而不是重新定位它。
当布局使用基线对齐时,这会特别混乱。因为只有文本视图在固有内容尺寸下显示时,基线对齐才有效。如果系统调整其中一个视图的大小,文本可能不会正确排列,尽管有一个必需的基线约束。

2.3.3 固定高度的列

本节扩展“简单的标签和文本框”一节为多列标签和文本框。这里,所有标签的结尾边缘是对齐的。文本框的开头和结尾边缘是对齐的,并且水平布局基于最长的标签。与“简单的标签和文本框”一节类似,这一节假设文本框总比标签高来简化布局。

2.3.3.1 视图和约束

布局标签和文本框,然后设置以下约束。

  1. First Name Label.Leading = Superview.LeadingMargin
  2. Middle Name Label.Leading = Superview.LeadingMargin
  3. Last Name Label.Leading = Superview.LeadingMargin
  4. First Name Text Field.Leading = First Name Label.Trailing + Standard
  5. Middle Name Text Field.Leading = Middle Name Label.Trailing + Standard
  6. Last Name Text Field.Leading = Last Name Label.Trailing + Standard
  7. First Name Text Field.Trailing = Superview.TrailingMargin
  8. Middle Name Text Field.Trailing = Superview.TrailingMargin
  9. Last Name Text Field.Trailing = Superview.TrailingMargin
  10. First Name Label.Baseline = First Name Text Field.Baseline
  11. Middle Name Label.Baseline = Middle Name Text Field.Baseline
  12. Last Name Label.Baseline = Last Name Text Field.Baseline
  13. First Name Text Field.Width = Middle Name Text Field.Width
  14. First Name Text Field.Width = Last Name Text Field.Width
  15. First Name Text Field.Top = Top Layout Guide.Bottom + 20.0
  16. Middle Name Text Field.Top = First Name Text Field.Bottom + Standard
  17. Last Name Text Field.Top = Middle Name Text Field.Bottom + Standard

2.3.3.2 属性

在属性检查器中,设置以下属性。尤其是右对齐所有标签的文本。这让文本标签比它们的文本更长,并且在文本框旁边排列。

View Attribute Value
First Name Label Text First Name
First Name Label Alignment Right
First Name Text Field Placeholder Enter first name
Middle Name Label Text Middle Name
Middle Name Label Alignment Right
Middle Name Text Field Placeholder Enter middle name
Last Name Label Text Last Name
Last Name Label Alignment Right
Last Name Text Field Placeholder Enter last name

每一对标签的content hugging必须比文本框的高。界面生成器再次自动完成这项工作;但是你可以在尺寸检查器总确认这些优先级。

Name Horizontal hugging Vertical hugging Horizontal resistance Vertical resistance
First Name Label 251 251 750 750
First Name Text Field 250 250 750 750
Middle Name Label 251 251 750 750
Middle Name Text Field 250 250 750 750
Last Name Label 251 251 750 750
Last Name Text Field 250 250 750 750

2.3.3.3 讨论

本节基本是三个“简单的标签和文本框”布局拷贝,一个放置在另一个的顶部。但是需要做一些额外工作,让行正确排列。

首先,通过右对齐每一个标签的文本来简化问题。不管文本的长度是多少,现在让所有标签的宽度相等,这样可以很容易的对齐它们的结尾边缘。另外,因为标签的compression resistance比它的content hugging大,所以所有标签更可能被拉伸,而不是压缩。对齐开头和结尾边缘,所有标签自动拉伸到最常标签的固有内容尺寸。

因此,你只需要对齐所有标签的开头和结尾边缘。同时还需要对齐所有文本框的开头和结尾边缘。幸运的是,标签的开头边缘已经对齐到父视图的开头页边留白。类似的,文本框的结尾边缘都对齐到父视图的结尾页边留白。因为所有行的宽度相同,所以你只需要排列另外两个边缘的其中一个,所有都会对齐。

有许多在方式完成这个工作。本节指定了所有文本框的宽度相同。

2.3.4 动态高度的列

本节组合你在“动态高度的标签和文本框”和“固定高度的列”中学习到的东西。本节的目标包括:

  • 根据最长标签的长度,对齐每个标签的结尾边缘。
  • 文本框的宽度相同,并且对齐它们的开头和结尾边缘。
  • 文本框扩展填充父视图剩余的所有空间。
  • 根据行中最高的元素定义行之间的高度。
  • 所有都是动态的,所以,如果字体尺寸和标签文本改变,布局会自动更新。

2.3.4.1 视图和约束

根据"固定高度的列"那样布局标签和文本框;但是需要一些额外的约束。

  1. First Name Label.Leading = Superview.LeadingMargin
  2. Middle Name Label.Leading = Superview.LeadingMargin
  3. Last Name Label.Leading = Superview.LeadingMargin
  4. First Name Text Field.Leading = First Name Label.Trailing + Standard
  5. Middle Name Text Field.Leading = Middle Name Label.Trailing + Standard
  6. Last Name Text Field.Leading = Last Name Label.Trailing + Standard
  7. First Name Text Field.Trailing = Superview.TrailingMargin
  8. Middle Name Text Field.Trailing = Superview.TrailingMargin
  9. Last Name Text Field.Trailing = Superview.TrailingMargin
  10. First Name Label.Baseline = First Name Text Field.Baseline
  11. Middle Name Label.Baseline = Middle Name Text Field.Baseline
  12. Last Name Label.Baseline = Last Name Text Field.Baseline
  13. First Name Text Field.Width = Middle Name Text Field.Width
  14. First Name Text Field.Width = Last Name Text Field.Width
  15. First Name Label.Top >= Top Layout Guide.Bottom + 20.0
  16. First Name Label.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
  17. First Name Text Field.Top >= Top Layout Guide.Bottom + 20.0
  18. First Name Text Field.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
  19. Middle Name Label.Top >= Top Layout Guide.Bottom + Standard
  20. Middle Name Label.Top = Top Layout Guide.Bottom + Standard (Priority 249)
  21. Middle Name Text Field.Top >= Top Layout Guide.Bottom + Standard
  22. Middle Name Text Field.Top = Top Layout Guide.Bottom + Standard (Priority 249)
  23. Last Name Label.Top >= Top Layout Guide.Bottom + Standard
  24. Last Name Label.Top = Top Layout Guide.Bottom + Standard (Priority 249)
  25. Last Name Text Field.Top >= Top Layout Guide.Bottom + Standard
  26. Last Name Text Field.Top = Top Layout Guide.Bottom + Standard (Priority 249)

2.3.4.2 属性

在属性检查器中,设置以下属性。尤其是右对齐所有标签的文本。这让文本标签比它们的文本更长,并且在文本框旁边排列。

View Attribute Value
First Name Label Text First Name
First Name Label Alignment Right
First Name Text Field Placeholder Enter first name
Middle Name Label Text Middle Name
Middle Name Label Alignment Right
Middle Name Text Field Placeholder Enter middle name
Last Name Label Text Last Name
Last Name Label Alignment Right
Last Name Text Field Placeholder Enter last name

每一对标签的content hugging必须比文本框的高。界面生成器再次自动完成这项工作;但是你可以在尺寸检查器总确认这些优先级。

Name Horizontal hugging Vertical hugging Horizontal resistance Vertical resistance
First Name Label 251 251 750 750
First Name Text Field 250 250 750 750
Middle Name Label 251 251 750 750
Middle Name Text Field 250 250 750 750
Last Name Label 251 251 750 750
Last Name Text Field 250 250 750 750

2.3.4.3 讨论

本节简单的组合“动态高度的标签和文本框”和“固定高度的列”中描述的技术。类似“动态高度的标签和文本框”,本节使用一对约束动态设置行之间的垂直间隔。类似“固定高度的列”,本节右对齐标签的文本,并显式指定相同宽度的约束。

提示:这个例子中,视图和顶部布局向导的距离为20.0个点,兄弟视图之间为8.0个点。这跟设置固定20个点的顶部页边留白效果一样。如果想要页边留白根据栏是否显示动态调整,必须添加额外的约束。通常使用“自适应的单个视图”中的技术。具体的实现留给读者完成。

正如你所看到的,布局的逻辑开始变得复杂;但是有一些方法可以让你简化工作。首先,正如前面描述的,应该尽可能使用堆栈视图。另外,你可以分组控件,然后布局控件。这可以把单个复杂的布局变为更小,更容易管理的块。

2.3.5 两个宽度相等的按钮

本节描述布局两个相同尺寸的按钮。在垂直方向上,按钮在屏幕底部对齐。在水平方向上,按钮拉伸填充所有的可用空间。

2.3.5.1 视图和约束

在界面生成器中,拖拽两个按钮到场景中。使用屏幕底部的向导线对齐。不用担心按钮的宽度相等——只需要拉伸其中一个填充剩余的水平空间。大致放置它们后,设置以下约束。自动布局会计算它们正确的最终位置。

  1. Short Button.Leading = Superview.LeadingMargin
  2. Long Button.Leading = Short Button.Trailing + Standard
  3. Long Button.Trailing = Superview.TrailingMargin
  4. Bottom Layout Guide.Top = Short Button.Bottom + 20.0
  5. Bottom Layout Guide.Top = Long Button.Botton + 20.0
  6. Short Button.Width = Long Button.Width

2.3.5.2 属性

给按钮指定一个可见的背景色,设备旋转时,可以更容易的看见它们frame的变化。另外,两个按钮使用不同长度的标题,用来展示按钮的标题不会影响按钮的宽度。

View Attribute Value
Short Button Background Light Gray Color
Short Button Title short
Long Button Background Light Gray Color
Long Button Title Much Longer Button Title

2.3.5.3 讨论

计算布局时,本节使用按钮的固有高度,而不是宽度。水平方向上,显式指定按钮的尺寸,让它们的宽度相等,并填充可用空间。比较本节和“两个宽度相等的视图”,查看按钮的固有高度如何影响布局。本节只有两个垂直约束,而不是四个。

按钮标题的长度总是不同,帮助说明按钮的文本如何影响(或者说不影响)布局。

提示:本节中,指定按钮的背景色为浅灰色,让你可以看清它们的frames。通常,按钮和标签有透明的背景,很难(甚至不可能)看清它们frame的任何变化。

2.3.6 三个宽度相等的按钮

本节扩展“两个宽度相等的按钮”,使用三个宽度相等的按钮。

2.3.6.1 视图和约束

如下布局按钮和设置约束。

  1. Short Button.Leading = Superview.LeadingMargin
  2. Medium Button.Leading = Short Button.Trailing + Standard
  3. Long Button.Leading = Medium Button.Trailing + Standard
  4. Long Button.Trailing = Superview.TrailingMargin
  5. Bottom Layout Guide.Top = Short Button.Bottom + 20.0
  6. Bottom Layout Guide.Top = Medium Button.Bottom + 20.0
  7. Bottom Layout Guide.Top = Long Button.Bottom + 20.0
  8. Short Button.Width = Medium Button.Width
  9. Short Button.Width = Long Button.Width

2.3.6.2 属性

给按钮指定一个可见的背景色,设备旋转时,可以更容易的看见它们frame的变化。另外,两个按钮使用不同长度的标题,用来展示按钮的标题不会影响按钮的宽度。

View Attribute Value
Short Button Background Light Gray Color
Short Button Title Short
Medium Button Background Light Gray Color
Medium Button Title Medium
Long Button Background Light Gray Color
Long Button Title Long Button Title

2.3.6.3 讨论

添加额外的按钮需要添加三个额外的约束(两个水平约束和一个垂直约束)。记住,你没有使用按钮的固有宽度,所以至少需要链各个水平约束,来唯一指定它的位置和尺寸。但是,你使用了按钮的固有高度,所以只需要一个额外的约束,来指定它的垂直位置。

提示:选中三个按钮,然后使用界面生成器的Pin工具创建一个相同宽度的约束,可以快速设置相同宽度约束。界面生成器自动创建所有必需的约束。

2.3.7 两个间隔相等的按钮

表面看来,本节跟“两个宽度相等的按钮”相似。但是,本节中按钮的宽度基于最长的标题。如果有足够的空间,按钮会被拉伸,直到它们都匹配更长按钮的固有内容尺寸。剩余的额外空间均匀分配在按钮周边。

在iPhone的竖屏方向上,“两个宽度相等的按钮”和“两个间隔相等的按钮”看起来几乎完全一样。当旋转到横屏(或者使用更大的设备,比如iPad)时,差别会变得很明显。

2.3.7.1 视图和约束

在界面生成器中,拖拽并放置两个按钮和三个视图对象。在视图之间放置按钮,然后设置以下约束。

  1. Leading Dummy View.Leading = Superview.LeadingMargin
  2. Short Button.Leading = Leading Dummy View.Trailing
  3. Center Dummy View.Leading = Short Button.Trailing
  4. Long Button.Leading = Center Dummy View.Trailing
  5. Trailing Dummy View.Leading = Long Button.Trailing
  6. Trailing Dummy View.Trailing = Superview.TrailingMargin
  7. Bottom Layout Guide.Top = Leading Dummy View.Bottom + 20.0
  8. Bottom Layout Guide.Top = Short Button.Bottom + 20.0
  9. Bottom Layout Guide.Top = Center Dummy View.Bottom + 20.0
  10. Bottom Layout Guide.Top = Long Button.Bottom + 20.0
  11. Bottom Layout Guide.Top = Trailing Dummy View.Bottom + 20.0
  12. Short Button.Leading >= Superview.LeadingMargin
  13. Long Button.Leading >= Short Button.Trailing + Standard
  14. Superview.TrailingMargin >= Long Button.Trailing
  15. Leading Dummy View.Width = Center Dummy View.Width
  16. Leading Dummy View.Width = Trailing Dummy View.Width
  17. Short Button.Width = Long Button.Width
  18. Leading Dummy View.Height = 0.0
  19. Center Dummy View.Height = 0.0
  20. Trailing Dummy View.Height = 0.0

2.3.7.2 属性

给按钮指定一个可见的背景色,设备旋转时,可以更容易的看见它们frame的变化。另外,两个按钮使用不同长度的标题。按钮基于最长的标题调整大小。

View Attribute Value
Short Button Background Light Gray Color
Short Button Title Short
Long Button Background Light Gray Color
Long Button Title Much Longer Button Title

2.3.7.3 讨论

正如你所看到的,约束集变复杂了。这个例子是为了阐述一个具体的技术,在实际应用程序中,应该考虑使用堆栈视图。

这个例子中,当父视图的frame改变时,你希望空白区域的尺寸跟着变化。这意味着你需要一组相同宽度的约束来控制空白区域的宽度;但是,你不能在空区域创建约束。必须有一个你可以约束尺寸的对象。

本节中,使用虚拟的视图表示空的区域。这些视图是UIView类实例,并设置它们的高度为0个点,最小化它们对视图层级结构的影响。

提示:虚拟视图会显著的影响布局成本,所有要慎重的使用它们。如果这些视图很大,尽管它们不包括任何有意义的信息,但它们的图形上下文还是会消耗大量的内存。
另外,这些视图参与视图层级结构的响应链。这意味着它们会响应沿着响应链传递的消息(比如点击测试)。如果不小心处理,这些视图会拦截并响应这些消息,从而出现很难查找的bug。

另外,你可以使用UILayoutGuide类实例表示空白区域。这个轻量级的类表示一个矩形框架(frame),可以参与到自动布局约束中。布局向导没有图形上下文,也不是视图层级结构的一部分。这让布局向导成为分组项或定义空白区域最理想的工具。

不幸的是,不能再界面生成器中添加布局向导,并且混用代码和故事版创建场景会很复杂。一个通用规则是,使用故事版和界面生成器,而不是自定义布局向导。

本节使用大于等于约束设置按钮周边的最小空间。必需的约束保证按钮的宽度总是相同,并且虚拟视图的宽度总是相同(但是按钮和虚拟视图的宽度可以不同)。其余布局主要由按钮的CHCR优先级管理。如果没有足够的空间,虚拟视图收缩为0个点宽度,并且按钮平均分配它们之间的可用空间(它们之间为标准间隔)。当可用空间增加时,按钮扩展到最大按钮的固有宽度,然后虚拟视图开始扩展,直到弹出剩余的空间。

2.3.8 两个基于尺寸类(Size Class)布局的按钮

本节使用两组不同的约束。一个安装在Any-Any布局。这些约束定义了一对宽度相等的按钮,与“两个宽度相等的按钮”中完全一样。

另一个约束安装在Compact-Regular布局中。这些约束定义了一对堆放按钮,如下所示。

垂直堆放按钮用在iPhone的竖屏方向上。水平行按钮用在其它地方。

2.3.8.1 约束

像“两个宽度相等的按钮”中布局按钮。在Any-Any尺寸类中,设置1到6的约束。

接着切换到界面生成器的Compact-Regular布局的尺寸类中。

卸载约束2和约束5,并添加约束7,8,9,如下所示。

  1. Short Button.Leading = Superview.LeadingMargin
  2. Long Button.Leading = Short Button.Trailing + Standard
  3. Long Button.Trailing = Superview.TrailingMargin
  4. Bottom Layout Guide.Top = Short Button.Bottom + 20.0
  5. Bottom Layout Guide.Top = Long Button.Botton + 20.0
  6. Short Button.Width = Long Button.Width
  7. Long Button.Leading = Superview.LeadingMargin
  8. Short Button.Trailing = Superview.TrailingMargin
  9. Long Button.Top = Short Button.Bottom + Standard

2.3.8.2 属性

给按钮指定一个可见的背景色,设备旋转时,可以更容易的看见它们frame的变化。另外,两个按钮使用不同长度的标题,用来展示按钮的标题不会影响按钮的宽度。

View Attribute Value
Short Button Background Light Gray Color
Short Button Title short
Long Button Background Light Gray Color
Long Button Title Much Longer Button Title

2.3.8.3 讨论

界面生长器可以设置尺寸类的特定视图,视图属性和约束。它允许你为不同的尺寸类(紧凑,任意,或常规)的宽度和高度指定不同的选项,总共有9个不同的尺寸类。其中,四个对应最终(Final)设备使用的尺寸类(Compact-Compact,Compact-Regular,Regular-Compact,以及Regular-Regular)。其余的是基础(Base)尺寸类,或者说抽象表示两个或多个尺寸类(Compact-Any,Regular-Any,Any-Compact,Any-Regular,以及Any-Any)。

为给定尺寸类加载布局时,系统为该尺寸类加载最确切的设置。这意味着Any-Any尺寸类定义了所有视图使用的默认值。Compact-Any设置影响所有紧凑宽度的视图,Compact-Regular设置只在紧凑宽度和常规高度的视图中使用。当视图的尺寸类改变时(例如,iPhone从竖屏旋转到横屏),系统系统切换布局,并动画的改变。

可以使用该特性为不同的iPhone方向创建不同的布局。也可以使用它创建不同的iPad和iPhone布局。具体尺寸类的自定义可以跟你希望的那么简单或广泛。当然,拥有更多变化,故事版就会变得更复杂,设计和维护也就更困难。

记住,你需要确保每个可能的尺寸类都有一个有效的布局,包括所有基础尺寸类。一个通用规则是,选择一个布局作为默认布局。在Any-Any尺寸类中设计该布局。然后根据需要修改Final尺寸类。记住,你可以在更具体的尺寸类中同时添加和移除项。

对于更复杂的布局,你可能希望先取出尺寸类的9×9网格。用布局填充这些尺寸类的四个角。然后网格让你查看哪个约束在多个尺寸类终共享,并帮助你找出布局和尺寸类的最佳组合。

更多使用尺寸类的信息,请参考“调试自动布局”。

推荐阅读更多精彩内容