安路的技术博客

Nothing in life is to be feared. It is only to be understood

通过一个Demo详解UIStackView

| Comments

在上一个章节我们已经介绍了什么是UIStackView了,其实它更类似于Android开发中的LinerLayer排版技术。

这一章节,我们通过一个完整的例子来讲解UIStackView的用法

开始

下下载这个开始工程,下载完毕后,用Iphone6 模拟器运行起来,你将会看到一个度假旅游的列表

logo 点击第一行cell,咋看,这个视图没有什么问题,但是你仔细观察,就会发现有几个问题:

  1. 看视图的下面的那一排按钮,它们中间都有一定间隙规则布局,但是它们并没有适配整个屏幕的布局,看着挺丑的,临时转换屏幕landscape orientation,通过 Command-left

logo

  1. 在详情页面,点击hidden按钮,它成功地隐藏了文字,但是下面的内容并没有顶上去,中间一片空白 logo

现在你已经有几点好的建议去提升app的体验,现在让我们开始切入这个工程

打开Main.storyboard然后找到 Spot Info View Controller,这里有一些颜色在stackView中。

logo

这些标签和按钮已经有几种不同颜色的背景色,但是在运行时他们的背景色就是透明的,在这个storyboard中,他们仅仅是为了帮助你展示stackView是怎么改变属性影响嵌套的子视图

你不需要做这些,但是从另一个观点来说你实际上喜欢去看看这些背景色当运行程序的时候,你能临时做些改变在SpotInfoViewController的viewDidLoad()中:

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

当然,其它标签都有占位符文字说明,他们仅仅是为了让你区分哪些是和后台连接的。哪些是描述什么内容的。例如<whyVisitLabel>是连接

1
@IBOutlet weak var whyVisitLabel: UILabel!

另外一个需要注意的是在这个storyboard中不是默认的 600 x 600,当你使用SizeClass的时候。

SizeClass总是可用的,但是初始化Navigation Controller 默认是总是iPhone 4-inch在模拟器下,这个是容易的在storyboard中,这个模拟器在启动的时候是不受影响的,这个视图将会动态适应不同的设备。 logo

你的第一个StackView

第一件事是你将通过一个stackView修复最下面一排按钮的间距,一个stackView能描述在不不同轴向的布局(横向坐标和纵向坐标),其中之一就是子视图之间的距离设置。

幸运的是,修改已经存在的View在一个stackView中并不复杂,选中 Spot Info View Controller底部的所有按钮

检查这三个按钮是不是都选择上了,打开左边的控件面板查看, logo

一旦选中了,在storyboard的右下角点击new Stack button logo

这个按钮将会变成嵌入式的在stackView中

logo

这些按钮看起来不是平滑的,稍后我们将会修复

当这个stackView开始嵌套这些按钮的时候,我们将要添加自动布局给这个stackView

当你嵌套一个视图在一个stackView中,这个视图的任何约束都会被移除,例如,在嵌套到stackView之钱,在最前面的那个按钮Submit Rating有个垂直距离的约束和Rating:label之间: logo

点击Submit Rating按钮去看看是否还有这个约束: logo

为了给stackView添加约束,首先你必须选中它,一个简单的方式去选择这个stackView在outline View: logo 另外一种方式是按住 shift&Right-click 在你想选择的视图上,或者按钮 control+shift+左键点击在你想要选择的视图上,你将会看到一个菜单视图,供你选择。 logo

现在,点击pin按钮在自动布局的工具条上 去 添加约束。

首先检查Constrain to margins,然后添加下载的约束:

Top: 20, Leading: 0, Trailing: 0, Bottom: 0

logo

现在,这个stackVeiw是正确的尺寸,但是它有一点拉伸第一个按钮,因为它要去利用多余的空间。

stackView有个distribution属性来决定子视图的布局,当前,它是fill,这意味着包含的子视图都会完全填充stackview剩下的空间,为了修补这个,这个stackView将要展开其中的一个子视图去填补这个多余的空间

然而,你并不期望这个按钮完全填充stackView,你想让他们占用相同的空间。

选中这个stackView,然后修改它的属性DistributionFillEqual Spacing: logo

现在编译运行,点击这个cell.旋转屏幕,你将会看到底部的按钮平分在屏幕的底部,是不是很酷呢! logo

如果不使用stackView来解决这个问题,你不得不使用sapce views,在没两个按钮之间,你的加入等比宽度的约束。很是麻烦。 它看起来像是下面这样,这中间的space View看起来有点灰色

logo

这个问题还是不是很大在一个storyboard中,但是很多视图都是动态的,这就不是一个简单的任务了,在运行时去增加一个按钮或者隐藏一个按钮,因为需要去调节视图和约束之间的关系。

为了在一个stack view中隐藏一个视图,你不得不设置子视图的hidden属性为true.现在你将要修复之前说过的那个问题,就是当点击 hidden之后,文字消失,下面的多余空间要顶上去。

转换Sections

你将要转换所有的section用stack view中,这将要确保你容易的完成你的任务,下一步你将要转换 rating section.

Rating section

定位到你刚才的页面,然后选择RATING标签和 星星的视图: logo 然后点击 stack按钮让其嵌套在一个stackView中。 logo 现在,点击PIN按钮,添加下面三个属性:

 Top: 20, Leading: 0, Bottom: 20

logo

现在切换到Attributes inspector,设置 spacing为8: logo

这时,你看到视图上的两个控件之间已经有些间距了, logo 有时候,xcode可能提示你stackview的位置是不正确的,但是这些警告将会消失,当你更新其它控件的时候,你通常可以忽略他们。

为了证明这个,改变 AlignmentFillTop然后再改为Fill,你将会看到这个stars 标签变成正确的位置了。

编译运行你的app,一切看起来还是和从前一样.

取消嵌套一个Stack View

在你深入学习之前,去进行一些"急救"训练,有时,你会发现你的视图上有一个你不再需要的stackview,或许你为了练习而导致的事故。

幸运的是,这里有容易的方式去移除一个嵌套的view从stack view中。

首先,你最好选择你想要删除的stack view,按住Option键,然后点击 stack按钮,点击 Unembed菜单就可以了: logo

你的第一个垂直Stack view

现在,你将要创建一个垂直的stack view,选中WHY VISIT标签和<whyVisitLabel>logo Xcode将会正确的推断出这个视图将会需要一个垂直的stack view,点击Stack按钮去嵌套它们到一个stack view中。

当stack view添加成功之后,嵌套的视图的约束将会给删除,当前的这个stack view没有任何约束,所以它会适配子视图中最大尺寸的。

当这个stack view选中的时候,点击 Pin按钮,设置如下属性: Top, Leading and Trailing 都为0

然后,点击dropdown在右下角,然后选择WEATHER (current distance = 20):

logo

最后,添加这4个约束,你将会看到如下结果: logo 现在你有两个一个展开的stackview,它的右边界是定位到了视图的右边界,然而,这下面的标签依然是同样宽的,你将要修复它通过stack view的alignment属性

Alignment属性

这个alignment属性决定了stack view在其轴向上的布局方式,可能是Fill,Leading,Center和Trailing.

logo

在垂直的stackview中,选择不同的属性,将会看到不同的布局:

Fill: logo

Leading: logo

Center: lgo

Trailing: lgo

当你测试完每个属性值后,最后设置成Fill

编译运行程序看起来是OK的,特别需要指出的是,Fill意味着你想要所有的视图都是完全占用空间在其轴向上,这将引起WHY VISIT标签去展开它到右边缘。

但是如果你只想下面的label张开到右边缘,此时该怎么做呢?

转换"what to see"模块

这个转换和上面的那个很相似,介绍如下:

  1. 首先,选择WHAT TO SEE标签和<whatToSeeLabel>
  2. 点击Stack按钮
  3. 点击Pin按钮
  4. 设置margins约束,添加下面4个约束
1
Top: 20, Leading: 0, Trailing: 0, Bottom: 20
  1. 设置stack view的Alignment为FIll

logo 编译运行你的工程,验证页面是不是看起来和之前一样。

剩下就是这个weather模块了

转换weather模块

这个weather模块比其他几个稍微复杂一些,因为它包含了一个hidden按钮

一种方法是你将会创建一个最近的stacview通过嵌套WEATHER标签和Hide按钮在一个水平的stackview中,然后嵌套水平的stackview和<weatherInfoLabel>到一个垂直的stackview中。

看起来你像是这样: lgo

点击Stack按钮: logo

然后点击 Pin按钮,设置margin约束,设置如下:

1
Top: 20, Leading: 0, Trailing: 0, Bottom: 20

设置 stack view的Alignment为Fill logo

你需要一个在hide按钮和WEATHER标签的右边加一个约束,因为WEATHER标签被加到了stack view中,它的所有约束都被自动去掉了.

然后,你希望底部的<weatherInfoLabel>去填充整个stack view.

你可以完成这个通过把WEATHER嵌套进一个垂直的stack view中,记住垂直stackview可以设置alignment为 .Leading,假如stack view是拉伸的超出了它固有的边界,它包含的子视图将会到达它的边界。

选择WEATHER标签通过document outline,或者通过Control-Shift-click方法: logo

点击 stack按钮: lgo

设置Alignment为Leading,然后确保axis是垂直方向: log

完美!你已经完成了外部的stackview平铺,在嵌套的stackView中去填充它的宽度,但是内部的stackview允许这个标签去保持它原有的宽度。

编译运行,为什么hide按钮现在飘到上面去了呢? dlo

它是因为当你嵌套WEATHER标签到一个stackview中时,它的所有和hide按钮相关的约束都被移除掉了。

现在你需要给hide按钮和WEATHER标签之间增加新的约束,按住control-drag从Hide按钮拖向WEATHER标签: logo

添加两个约束:

  1. Horizontal Spacing
  2. Baseline

logo 编译运行,这个Hide按钮看起来正常了。

现在所有的模块都是在唯一的stackview中了,你把他们全部都嵌套进了stackview中。

设置第一级Stack view

点击 command 然后选择所有的5个顶级的stackview在 outline view中 log

然后点击stack按钮: lgo

点击Pin按钮,设置约束属性: 全部都设置成0.然后设置Spacing为20, Alignment为Fill,你的storyboard看起来像是这样: g

编译运行,此时你的 hide按钮又跑偏了,和之前设置的一样,需要把hide和WEATHER重新建立约束: d

编译运行,此时hide按钮在正确的位置上了。

重新布局视图

现在所有的的模块都在顶级的stackview中,你现在可以更改what to see模块的位置,比如和weather模块的位置进行互换。

选择middle stack view从outline view然后拖拽它和weather的那个stackview进行互换, log

此时,weather模块是第三个模块,但是这个 hide按钮不是在stackview中,它不会被移动。

选中 Hide按钮 : logo 然后点击Resolve Auto Layout Issues在自动布局的菜单上点击 update frame: logo

Size class based configuration

最后,你能把你注意力集中到之前的任务清单上,在加载模式中,垂直空间是昂贵的,所以你想让stackview中的模块靠近些,为了做到这些,你将要使用size classes去设置顶部stackview的空间从20修改成10.

选中顶部的stackview然后点击小小的+号,设置spacing:

log

选择 Any Width > Compact Height:

lgo

然后设置Spacing 为10,在 new wAny hC文本框中: lgo

动画

打开SpotInfoViewController.swift文件,然后找到updateWeatherInfoViews(hideWeatherInfo:animated:).方法

你将要替换这一行代码:

1
weatherInfoLabel.hidden = shouldHideWeatherInfo

改成如下:

1
2
3
4
5
6
7
if animated {
  UIView.animateWithDuration(0.3) {
    self.weatherInfoLabel.hidden = shouldHideWeatherInfo
  }
} else {
  weatherInfoLabel.hidden = shouldHideWeatherInfo
}

编译运行,点击hideshow按钮,会不会感觉出来有点动画效果呢?

在stackview中增加动画效果也是很容易的,比如hidden, alignment, distribution, spacing,甚至axis。

完整工程下载

你可以下载完整的工程 完整工程

希望能够帮到你~

Comments