文章目录
  1. 1. Getting Started
    1. 1.1. Your first stack view
    2. 1.2. 一些思考
  2. 2. Layout anchors
  3. 3. Layout guides
    1. 3.1. Fixing the alignment bug
    2. 3.2. 处理截断问题

声明:本文章版权归作者所有,转载请注明出处。
孟祥月的博客:http://www.mengxiangyue.com
http://blog.csdn.net/mengxiangyue

今天这是第六篇笔记,现在回过头去看,我也没有想到自己能够更新到第六篇。我算是一个比较懒的人了,现在已经不太喜欢动手敲代码了。在写这几篇笔记的时候,我需要一边看英文的文档,一边测试代码,还得考虑怎么能够写明白。这里有点说明,我的英语水平只是四级,语文水平只能用呵呵评价了,文章中的语句难免会有不通顺的地方,希望能够把语义表述清楚。

闲话多了,回到正题,这篇文章介绍UIStackView和一些Auto Layout的改变。

UIStackView我个人理解是为了解决使用Storyboard添加的约束需要经常变化的情况。我想我们可能都在开发中遇到过修改约束的情况,一般是把约束与一个outlet的约束link起来,然后代码修改,但是这个操作起来是不方便的。UIStackView通过修改一些简单的属性,例如alignment, distribution, and spacing,从而让UIStackView根据我们的修改自动调整内部的显示。

Auto Layout的改变主要是介绍layout anchors和layout guides。

Getting Started

打开本章的配套的工程VacationSpots,在iPhone 6模拟器上运行,能够看到APP有一些UI的问题,不要担心,在后面将会修复这些问题。简单梳理一下问题如下:

  1. 图中标出的内容没有在垂直方向居中

  2. 点击列表中的London Cell进入详情页面,最下面的三个按钮没有平均分配空间:

  3. 点击WEATHER旁边的hide按钮,内容是被隐藏了,但是留下了一块空白,下面的内容没有移动上来:

  4. WHAT TO SEE 部分在WHY VISIT的下面会更加合理一点。

现在已经了解了这些问题,下面开始用UIStackView来修改这些问题。打开Main.storyboard,查看如下的Controller scene:

能够注意到上面的每个对应的控件都有背景颜色,这个只是为了帮助我们查看这些属性的变化。这些背景颜色在运行的时候都会被去掉,通过如下代码,如果你想让它们在运行的时候也显示注释掉这些代码就可以:

1
2
3
4
5
6
7
8
9
10
11
// SpotInfoViewController.swift
override func viewDidLoad() {
super.viewDidLoad()

// Clear background colors from labels and buttons
for view in backgroundColoredViews {
view.backgroundColor = UIColor.clearColor()
}

........
}

在Storyboard中的的控件都通过outlet与SpotInfoViewController.swift中对应的属性进行了关联。在storyboard中显示的名字对应SpotInfoViewController.swift对应的变量。

Your first stack view

我们先用Stack View解决我们问题列表中底部按钮的问题。使用UIStackView能够在一个坐标抽上分配位置和控件之间的空间。幸运的是将Views嵌入到UIStackView中并没有太难。在Storyboard中的Spot Info View Controller选择如下三个按钮控件:

选择好三个按钮后,在Storyboard中点击如下的按钮:

当Views被嵌入UIStackView之后,Views的约束都被移除了,同时需要设置UIStackView的约束。选中UIStackView,然后按照如图添加约束:

这里有个选择UIStackView的技巧,由于UIStackView是在按钮的后面,很不好选中,我们可以按住Shift,右击,在出现的菜单中列出来当前点击位置所有的View,我们可以选择UIstackView。另一种方法我们可以在outline view中选择。

设置好约束后,能够看到按钮显示如下,第一个按钮被拉伸了,填充满了UIStackView的剩余空间。UIstackView有一个Distribution属性,用于控制Views怎么在UIStackView中显示,现在设置的是Fill,即将会填充满UIStackView。为了这个目的,UIstackView将会根据View的ccontent hugging优先级去拉伸View,最低的将会被拉伸。如果优先级一样,将会拉伸第一个。

我们的目的是让View间的距离相等,在Attributes inspector中修改Distribution为Equal Spacing。

运行APP,能够看到我们的按钮显示正确了:

一些思考

思考一下你使用Auto Layout,通过约束实现上面的要求,那将是一种什么令人”愉悦”的行为。可能你很熟悉Auto Layout,认为这些东西都很简单,那么你在考虑一下如果我们后面有需求添加一个按钮,删除一个按钮呢,怎么办呢?约束删除了重新添加吗?如果使用UIStackView,这些将变得比较简单,只要我们添加或者删除View,其他的工作UIstackView就会帮我们做了。

UISTackView的更加深入的讲解,将会在下一篇文章中继续介绍。这里先介绍一下Auto Layout的新特性:layout anchors和layout guides。

Layout anchors

Layout anchors提供了我们一种简单的创建约束的方式。

想象一下我们在iOS 9之前创建一个约束,简直就是天书,但是在iOS 9中使用Layout anchors将会简单好多,下面是两种的对比:

1
2
3
4
5
// iOS9以前
let constraint = NSLayoutConstraint(item: topLabel, attribute: .Bottom, relatedBy: .Equal, toItem: bottomLabel, attribute: .Top, multiplier: 1, constant: 8)

// iOS 9
let constraint = topLabel.bottomAnchor.constraintEqualToAnchor(bottomLabel.topAnchor, constant: 8)

Layout anchors不仅理解起来简单,而且写起来也简单了。

对应于我们在iOS 9以前添加约束时候的attribute,基本都有与之对应的anchor,例如top对应topAnchor,bottom对应bottomAnchor等。Layout Anchor都是直接或者间接继承自NSLayoutAnchor,上面只是演示了一下相等的情况,我们都知道在约束中又大于小于等,下面列出NSLayoutAnchor的接口文件,从接口文件中能够清楚的了解到对应的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import Foundation
import UIKit

/* NSLayoutAnchor.h
Copyright (c) 2015, Apple Inc. All rights reserved.
*/


/* An NSLayoutAnchor represents an edge or dimension of a layout item. Its concrete
subclasses allow concise creation of constraints.
Instead of invoking

+[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]

directly, you can instead do something like this:

[myView.topAnchor constraintEqualToAnchor:otherView.topAnchor constant:10];

The -constraint* methods are available in multiple flavors to support use of different
relations and omission of unused options.
*/


@available(iOS 9.0, *)
public class NSLayoutAnchor : NSObject {

/* These methods return an inactive constraint of the form thisAnchor = otherAnchor.
*/

public func constraintEqualToAnchor(anchor: NSLayoutAnchor!) -> NSLayoutConstraint!
public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutAnchor!) -> NSLayoutConstraint!
public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutAnchor!) -> NSLayoutConstraint!

/* These methods return an inactive constraint of the form thisAnchor = otherAnchor + constant.
*/

public func constraintEqualToAnchor(anchor: NSLayoutAnchor!, constant c: CGFloat) -> NSLayoutConstraint!
public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutAnchor!, constant c: CGFloat) -> NSLayoutConstraint!
public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutAnchor!, constant c: CGFloat) -> NSLayoutConstraint!
}

/* Axis-specific subclasses for location anchors: top/bottom, leading/trailing, baseline, etc.
*/


@available(iOS 9.0, *)
public class NSLayoutXAxisAnchor : NSLayoutAnchor {
}

@available(iOS 9.0, *)
public class NSLayoutYAxisAnchor : NSLayoutAnchor {
}

/* This layout anchor subclass is used for sizes (width & height).
*/


@available(iOS 9.0, *)
public class NSLayoutDimension : NSLayoutAnchor {

/* These methods return an inactive constraint of the form
thisVariable = constant.
*/

public func constraintEqualToConstant(c: CGFloat) -> NSLayoutConstraint!
public func constraintGreaterThanOrEqualToConstant(c: CGFloat) -> NSLayoutConstraint!
public func constraintLessThanOrEqualToConstant(c: CGFloat) -> NSLayoutConstraint!

/* These methods return an inactive constraint of the form
thisAnchor = otherAnchor * multiplier.
*/

public func constraintEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat) -> NSLayoutConstraint!
public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat) -> NSLayoutConstraint!
public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat) -> NSLayoutConstraint!

/* These methods return an inactive constraint of the form
thisAnchor = otherAnchor * multiplier + constant.
*/

public func constraintEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat, constant c: CGFloat) -> NSLayoutConstraint!
public func constraintGreaterThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat, constant c: CGFloat) -> NSLayoutConstraint!
public func constraintLessThanOrEqualToAnchor(anchor: NSLayoutDimension!, multiplier m: CGFloat, constant c: CGFloat) -> NSLayoutConstraint!
}

在上面的接口文件中,我们能够清楚的了解到NSLayoutAnchor有三个子类:NSLayoutXAxisAnchor,NSLayoutYAxisAnchor,NSLayoutDimension。下面列出了UIView的Anchor都是对应的那种类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
extension UIView {
/* Constraint creation conveniences. See NSLayoutAnchor.h for details.
*/

@available(iOS 9.0, *)
public var leadingAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
public var trailingAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
public var leftAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
public var rightAnchor: NSLayoutXAxisAnchor { get }
@available(iOS 9.0, *)
public var centerXAnchor: NSLayoutXAxisAnchor { get }

@available(iOS 9.0, *)
public var topAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
public var bottomAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
public var firstBaselineAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
public var lastBaselineAnchor: NSLayoutYAxisAnchor { get }
@available(iOS 9.0, *)
public var centerYAnchor: NSLayoutYAxisAnchor { get }

@available(iOS 9.0, *)
public var widthAnchor: NSLayoutDimension { get }
@available(iOS 9.0, *)
public var heightAnchor: NSLayoutDimension { get }
}

上面UIView的Anchor属性被分成了三类,同样我们在设置属性的时候,也要求同类的属性的才能设置,比如ViewA和ViewB之间的约束,ViewA-topAnchor和ViewB-bottomAnchor是可以的,ViewA-topAnchor和ViewB-leftAnchor就是不允许的,如果这样的话编译器会警告,运行时也会报错。

注意: whyVisitLabel.topAnchor.constraintEqualToAnchor(whatToSeeLabel.leftAnchor) 这个按照上面的说法应该会报错的,但是我在运行的时候也没有报错,可能是我这里只是随便写出一个做测试的原因,后续我会继续试验一下这个知识点,然后再改正。

Layout guides

有时候我们想设置两个View之间的空间,需要在两个View之间添加一个不可见的View(dummy view),然后在设置约束。Layout guide可以理解为一个隐形的不可见View,我们能够使用它的矩形边缘来布局,我们可以像我们使用View一样设置约束。使用Layout guide的好处是轻量,而且不会在view的层级中,也不会参与事件的响应过程。layout guide也包含除了firstBaselineAnchor和lastBaselineAnchor之外的View所有的Anchor。

Fixing the alignment bug

下面就利用layout guide来修复列表页面文字内容上下不居中的问题。看下图是我们设置的约束,我们设置label距离上面的距离是15,当下面的label显示一行的时候是正常的,如果要是两行了,由于上面的约束是固定的,最终就变成了不居中的效果了。

在iOS 9之前,我们想解决这个问题可以把两个label放置到一个container view容器中,设置这个container view为剧中,这里面的container view就是不可见的view即dummy view。现在在iOS 9上我们可以使用layout guide来代替这个view。

目前只能通过代码来添加layout guide。打开VacationSpotCell.swift文件,修改对应代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override func awakeFromNib() {
super.awakeFromNib()

// TODO: Add layoutGuide code here to center the name and locationName labels vertically

// 创建layou guide
let layoutGuide = UILayoutGuide()
contentView.addLayoutGuide(layoutGuide)

// 设置layout guide的约束
let topConstraint = layoutGuide.topAnchor.constraintEqualToAnchor(nameLabel.topAnchor)
let bottomConstraint = layoutGuide.bottomAnchor.constraintEqualToAnchor(locationNameLabel.bottomAnchor)
let centerConstraint = layoutGuide.centerYAnchor.constraintEqualToAnchor(contentView.centerYAnchor)

// 激活layout guide的约束
NSLayoutConstraint.activateConstraints([topConstraint, bottomConstraint, centerConstraint])
}

运行APP发现,部分文字被截断了:

处理截断问题

这个原因是我们设置layout guide居中,但是nameLabel(上面的)与super view top的约束还存在,造成了下面的label被挤压了,这时候我们只要删除掉这个约束就可以了。但是删除了之后storyboard会提示错误,这时候我们可以使用占位约束,这个主要是为了使storyboard不报错,在运行的时候并不会使用。

最近一直在加班,断断续续整理了好久终于整理完了这篇文章了,可能会有错误,还请大家指出。

文章目录
  1. 1. Getting Started
    1. 1.1. Your first stack view
    2. 1.2. 一些思考
  2. 2. Layout anchors
  3. 3. Layout guides
    1. 3.1. Fixing the alignment bug
    2. 3.2. 处理截断问题