版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.07.05 星期五 |
前言
iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
开始
首先看下写作环境
Swift 5, iOS 12, Xcode 10
主要内容:了解如何使用UIStackView简化iOS布局。 使用对齐,分布和间距水平或垂直布局一系列视图。
您是否曾经需要在运行时添加或删除视图中的视图并调整旁边的视图布局? 也许您调整了一些约束或使用第三方库来完成这项工作。 也许它不是运行时的东西,而是你希望在故事板中的其他视图之间插入一个新视图。
在这些情况下,您需要更改多个约束。 您可能会发现自己删除了该区域中的所有约束并重新添加它们。
UIStackView
简化了这些任务。 您可以在堆栈视图中轻松地水平或垂直布局一系列视图,并使用诸如对齐,分布和间距等属性将视图自动调整到可用空间的方式进行设置。 请享用!
打开准备好的项目材料。 使用iPhone 8 Simulator
在Xcode中构建并运行启动项目。 你会看到以下内容:
这是Vacation Spots
应用程序。 它建议了一系列可以远离它的地方。 在去任何地方之前,您将使用Stack Views
解决应用程序的一些问题。
1. Exploring Vacation Spots
点击London
,进入伦敦的信息视图。 乍一看,视图可能看起来不错,但它有一些问题:
- 1) 查看视图底部的按钮行。 由于它们之间的固定间距,它们不适应屏幕宽度。 要更好地查看问题,请按
Command-left
箭头将模拟器旋转到横向。
- 2) 点击
WEATHER
旁边的Hide
。 这会隐藏文本,但它不会重新布局它下面的部分,留下一块空白区域。
- 3) 区域内容需要改进。 如果在
WHY VISIT
之后出现了WHAT TO SEE
,而不是在它们之间定位天气部分,那将更符合逻辑。 - 4) 在横向模式下,底行按钮太靠近视图的下边缘。 如果减小
section
之间的间距,它看起来会更好 - 但仅限于横向模式。
现在您已经了解了将要进行的改进,现在是时候深入了解该项目。 打开Main.storyboard
。 它将以初始设备视图打开。 确保它将iPhone 8
显示为所选设备。
这在运行时没有任何影响,但可以帮助您了解屏幕在该设备上的外观。 您可以通过单击其他图标随时更改所选设备。 将鼠标悬停在图标上可显示其相应的设备。
注意:如果减小
Interface Builder
画布宽度(大约低于650
像素),则更改设备的UI会稍微改变。 设备列表折叠为Device
下拉列表:当按下时,它会显示垂直布局的可用设备列表:
现在,仔细查看Spot Info View Controller
:
你可能想知道颜色是什么?
这些标签和按钮具有不会在运行时显示的背景颜色。 在故事板中,它们是视觉辅助工具,可帮助显示更改堆栈视图的各种属性如何影响其嵌入视图的框架。
如果在运行应用程序时您想在任何时候查看背景颜色,您可以在SpotInfoViewController
内的viewDidLoad()
中临时注释掉以下行。
// Clear background colors from labels and buttons
for view in backgroundColoredViews {
view.backgroundColor = UIColor.clear
}
任何outlet
连接的label
都有一个与outlet
变量名称匹配的占位符文本。 这样可以更容易地确定哪些标签将在运行时更新其文本。 例如,带有文本<whyVisitLabel>
的标签连接到:
@IBOutlet var whyVisitLabel: UILabel!
是时候开始了!
Your First Stack View
首先,您将修复底行按钮之间的间距。 堆栈视图可以以各种方式沿其轴分布其子视图。 一种方法是在每个视图之间设置相等的间距。
幸运的是,将现有视图嵌入到新的stack view
中是件小事。 首先,单击Spot Info View Controller
场景底部的所有按钮,然后单击其中一个按钮,然后command-clicking
其他两个按钮。 使用故事板画布左下角的Show Document Outline
按钮打开大纲视图。
确认您已选择所有三个按钮。
它们在大纲视图中突出显示。 您还可以command-click
大纲视图中的每个按钮以选择它们。
选择后,单击故事板画布右下角的Auto Layout
工具栏中的Embed In
按钮。 将出现一个包含可用嵌入选项的菜单。 选择堆栈视图:
Xcode会将按钮嵌入到新的堆栈视图中。
1. Stack View Constraints
虽然堆栈视图负责定位按钮,但仍需要添加自动布局约束来定位堆栈视图本身。
在堆栈视图中嵌入视图时,它会丢失对其他视图的约束。 例如,在将按钮嵌入堆栈视图之前,Submit Rating
按钮的顶部在RATING
标签的底部有一个垂直间距约束:
选择Submit Rating
按钮并验证它是否不再附加任何约束:
您还可以查看Size inspector (Option-Command-5)
以验证没有约束:
有时,在拥挤的故事板的视图控制器中选择特定元素很困难。 您可以从outline view
中选择元素,或使用Shift
并右键单击或按住Control
键并单击要选择的视图。 这将为您提供一个上下文菜单,显示您单击的位置的视图层次结构。 在菜单中单击选择stack view
。
现在您已选择Stack View
,您可以为其添加约束。
单击Auto Layout
工具栏中的Add New Constraints
按钮以显示Add New Constraints
弹出窗口。
首先,为Constrain to margins
添加一个选中。 然后,将以下约束添加到堆栈视图的边缘:
Top: 20, Leading: 0, Trailing: 0, Bottom: 0
仔细检查top, leading, trailing, and bottom
约束的数字。 确保已打开四个红色I-beams
和Constrain to margins
。 然后,单击Add 4 Constraints
。
现在堆栈视图的大小正确,但它已拉伸第一个按钮以填充任何额外空间。
2. Stack View Appearance
确定堆栈视图如何沿其轴布置其子视图的属性是分布。 目前,它设置为Fill
,这意味着子视图将沿其轴完全填充堆栈视图。 要实现此目的,堆栈视图将仅展开其子视图之一以填充该额外空间。 具体来说,它对最低的horizontal content hugging priority
的视图进行扩展,或者如果所有优先级相等,则扩展第一个视图。
但是,您不希望按钮完全填充堆栈视图 - 您希望它们间隔相等。
确保仍然选中堆栈视图并转到Attributes inspector
检查器。 将Distribution
从Fill
更改为Equal Spacing
:
现在构建并运行,点击任何单元格,然后旋转模拟器。 您会看到底部按钮现在可以平等分配!
实现UI时,请始终询问自己元素是否适合可用空间。
以下是您的内容如何影响用户体验的示例:将Wikipedia
按钮的标题更改为Wikipedia website
并运行该应用程序。 将模拟器从纵向旋转到横向以查看差异。
在空间更紧凑的纵向方向上,最后一个按钮中的文本被剪裁,而在横屏中一切都很合适。
这是您经常遇到的问题,尤其是iPhone SE
等较窄的屏幕尺寸。
幸运的是,这是一个简单的解决方案。 在仍然选择堆栈视图的情况下,返回Attributes inspector
。 将比例从Equal Spacing
更改为Fill Proportionally
并将间距值更改为10
:
现在,构建并运行并检查两个方向。 你会发现一切都好了。
将按钮的名称更改回Wikipedia
。
恭喜,您已经构建了第一个堆栈视图!
3. Without a Stack View
为了在没有堆栈视图的情况下解决此间距问题,您必须在每对按钮之间使用一个间隔视图,为所有间隔视图添加相等的宽度约束,以及正确定位间隔视图的若干附加约束。
这看起来像下面这样。 为了在屏幕截图中显示,间隔视图显示浅灰色背景:
如果您必须在故事板中执行此操作,那么这不是一个问题,但许多视图都是动态的。 由于相邻的间隔视图和约束,在运行时添加新按钮或隐藏或删除现有按钮并不是一项简单的任务。
要在堆栈视图中隐藏视图,请将包含视图的hidden
属性设置为true
,并使用堆栈视图处理其余视图。 这是当用户隐藏其下方的文本时,您将如何修复WEATHER
标签下的间距。 将weather
部分标签添加到堆栈视图后,您将在本教程中执行此操作。
注意:您现在知道如何指定堆栈中子视图之间的间距。 但是如果你想在特定的子视图后有不同的间距呢? 从
iOS 11
开始,您可以使用 setCustomSpacing:afterView来实现。
Converting the Sections
接下来,您将转换SpotInfoViewController
中的所有其他部分以使用堆栈视图。 这使您可以完成剩余的任务。 从rating
部分开始。
1. Converting the Rating Section
在您创建的堆栈视图上方,选择RATING
标签和旁边的星号标签。
然后,单击Editor ▸ Embed in ▸ Stack View
。
现在,选择新创建的堆栈视图,然后再次单击Add New Constraints
按钮。 选中Constrain to margins
并添加以下三个约束:
Top: 20, Leading: 0, Bottom: 20
现在转到Attributes inspector
并将间距设置为8
。
注意:您可能已经注意到间距已经设置为
8
。以前,间距自动设置为24
,Xcode已经变得足够聪明,可以推断间距值以匹配嵌入视图之前的间距值。 很酷,对吗?
构建并运行以验证所有内容与以前相同。
2. Un-embedding a Stack View
在你走得太远之前,最好在出现问题时进行一些急救培训。 例如,您可能会发现自己有一个额外的堆栈视图,可能是因为实验,重构或意外。
幸运的是,从堆栈视图中可以轻松地取消嵌入视图。
首先,选择要删除的堆栈视图。 单击Embed In
按钮。 接下来,在出现的上下文菜单中选择Unembed
:
另一种方法是选择堆栈视图,然后从菜单中选择Editor ▸ Unembed
。
3. Creating a Vertical Stack View
现在,创建第一个垂直堆栈视图。 选择WHY VISIT
标签和其下方的<whyVisitLabel>
,然后选择Embed in ▸ Stack View
。
当您在堆栈视图中嵌入标签时,Xcode会根据标签的位置推断它应该是一个垂直堆栈。
较低的标签之前有一个约束将其固定到右边距,但是在堆栈视图中嵌入标签会删除该约束。 目前,堆栈视图没有约束,因此它采用其最大视图的固有宽度。
选择堆栈视图后,单击Add New Constraints
按钮。 将Top,Leading
和Trailing
约束设置为0
,再次确保选中Constrain to margins
。
然后,单击底部约束右侧的下拉菜单并选择WEATHER (current distance = 20)
:
默认情况下,Interface Builder
会向您显示最近邻居的约束,对于底部约束,它是距离为15
的Hide
按钮。您需要约束到其下方的WEATHER
标签。
最后,单击Add 4 Constraints
。 您现在应该看到以下内容:
现在您有一个扩展的堆栈视图,右边缘固定到视图的右边缘。 但是,底部标签的宽度仍然相同。 您将通过更新堆栈视图的alignment
属性来解决此问题。
4. Alignment Property
alignment
属性确定堆栈视图如何垂直于其轴放置其视图。 对于垂直堆栈视图,可能的值为Fill,Leading,Center
和Trailing
。
水平堆栈视图的可能alignment
值略有不同:
水平堆栈视图有.top
而不是.leading
,并且有.bottom
而不是.trailing
。 还有两个属性仅在水平方向有效,.firstBaseline
和.lastBaseline
。
选择每个值以查看它如何影响垂直堆栈视图中的标签放置:
Fill:
Leading:
Center:
Trailing:
完成对每个值的测试后,将Alignment
设置为Fill
:
构建并运行以验证一切看起来都很好并且没有回归。
指定Fill
意味着您希望所有视图都垂直于其轴填充堆栈视图。 这会导致WHY VISIT
标签也扩展到右边缘。
如果您只希望底部标签扩展到边缘怎么办?
现在,它并不重要,因为两个标签在运行时都有清晰的背景,但是当你转换weather
部分时它会很重要。
您将学习如何使用额外的堆栈视图来实现这一目标。
5. Converting the What to See Section
转换此部分与您已经完成的操作类似。
- 1) 首先,选择
WHAT TO SEE
标签和下面的<whatToSeeLabel>
。 - 2) 单击
Embed In
按钮,然后选择Stack View
。 - 3) 单击
Pin
按钮。 - 4) 选中
Constrain to margins
并添加以下四个约束:
Top: 20, Leading: 0, Trailing: 0, Bottom: 20
- 5) 将堆栈视图的
Alignment
设置为Fill
。
您的故事板现在应该如下所示:
这只剩下weather
部分了。
Converting the Weather Section
由于Hide
按钮,weather
部分比其他部分更复杂。
要转换weather
部分,您可以通过将WEATHER
标签和Hide
按钮嵌入水平堆栈视图来创建嵌套堆栈视图。 然后,将该水平堆栈视图和<weatherInfoLabel>
嵌入到垂直堆栈视图中。
它看起来像这样:
请注意,WEATHER
标签已扩展为等于Hide
按钮的高度。 这将导致WEATHER
标签的基线与其下方的文本之间产生额外的空间。
请记住,alignment
指定垂直于堆栈视图的位置。 因此,您可以将alignment
设置为Bottom
:
但是,您不希望Hide
按钮的高度决定堆栈视图的高度。
相反,您将从所有堆栈视图中删除Hide
按钮。
Hide
按钮仍将是顶级视图的子视图,您将从其中添加一个约束到WEATHER
标签 - 它将位于堆栈视图中。 没错,您将从堆栈视图外部的按钮添加约束到堆栈视图中的标签!
选择WEATHER
标签和它下面的<weatherInfoLabel>
,然后将它们堆叠在堆栈视图中。
单击Add New Constraints
按钮,选中Constrain to margins
并添加以下四个约束:
Top: 20, Leading: 0, Trailing: 0, Bottom: 20
将堆栈视图的Alignment
设置为Fill
:
您需要在Hide
按钮的左边缘和WEATHER
标签的右边缘之间存在约束,因此使用WEATHER
标签填充堆栈视图将不起作用。
但是,您确实希望底部的<weatherInfoLabel>
填充堆栈视图。
您可以通过将WEATHER
标签仅嵌入垂直堆栈视图来实现此目的。 请记住,垂直堆栈视图的alignment
可以设置为.leading
,如果您将堆栈视图拉伸超出其固有宽度,则其子视图将保持与前端对齐。
使用document outline
选择WEATHER
标签,或使用Control-Shift-click
方法。 然后,将其嵌入新的堆栈视图中。
将Alignment
设置为Leading
,并确保Axis
设置为Vertical
:
很好! 外部堆栈视图拉伸内部堆栈视图以填充宽度,但内部堆栈视图允许标签保持其原始宽度!
建立并运行,为什么Hide
按钮在文本中间悬空?
将WEATHER
标签嵌入堆栈视图时,它与Hide
按钮之间的任何约束都被删除。
要添加新约束,请按住Control
键并将Hide
按钮拖动到WEATHER
标签。
按住Shift
键选择多个选项,然后选择Horizontal Spacing
和First Baseline
。 然后按Enter
,或单击弹出视图外的任何位置:
构建并运行。 现在应该正确定位Hide
按钮。 由于设置为隐藏的标签嵌入在堆栈视图中,因此按Hide
会隐藏标签并调整其下方的视图 - 所有这些都无需手动调整任何约束。
现在所有部分都在独特的堆栈视图中,您可以将它们嵌入到外部堆栈视图中,这将使最后两个任务变得微不足道。
1. Top-level Stack View
按住Command
键并单击以在outline view
中选择所有五个顶级堆栈视图。
然后将它们全部嵌入到一个堆栈视图中:
单击Add New Constraints
按钮,选中Constrain to margins
并向所有边添加0
的约束。 然后将Spacing
设置为20
并将Alignment
设置为Fill
。 您的故事板场景现在应该如下所示:
构建并运行
当WEATHER
堆栈视图嵌入外部堆栈视图时,看起来隐藏按钮再次失去其约束。 没问题,只需按照以前的方式再次添加约束:
- 按住
Control
键并从Hide
按钮拖动到WEATHER
标签。 - 按住
Shift
键。 - 选择
Horizontal Spacing
和Baseline
。 - 按
Enter
,或单击弹出视图外的任何位置。
建立并运行。 Hide
按钮现在位于正确的位置。
2. Repositioning Views
现在所有部分都在顶级堆栈视图中,您将修改WHAT TO SEE
部分的位置,使其位于WEATHER
部分之上。
从大纲视图中选择middle stack view
,然后在第一个和第二个视图之间拖动它。
注意:将指针稍微保持在您之间拖动的堆栈视图的左侧,以便它仍然是外部堆栈视图的子视图。 小蓝圈应位于两个堆栈视图之间的左边缘,而不是右边缘:
3. Size Class Configurations
最后,将注意力转移到列表中的剩余任务。 在横向模式下,垂直空间非常重要,因此您希望将堆栈视图的各个部分更紧凑的放在一起。 为此,当垂直size classes
是紧凑的时,您将使用size classes
将顶级堆栈视图的间距设置为10而不是20。
选择顶级堆栈视图,然后在Attributes inspector
中,单击Spacing
旁边的+
按钮:
选择Any Width
和Compact Height
,然后选择Add Variation
。
在新的hC
字段中将Spacing
设置为10
:
建立并运行。 纵向模式下的间距应保持不变。 旋转模拟器并注意部分之间的间距已减小,按钮现在从视图底部有足够的空间:
如果你没有添加顶级堆栈视图,你仍然可以使用size classes
在分隔五个部分的四个约束中的每一个上将垂直间距设置为10
,但是在一个地方设置它不是更好吗?
你有更好的时间做一些有意义的事,比如动画!
Animation
目前,该应用程序在隐藏和显示天气细节时非常跳跃。您将添加一些动画以平滑过渡。
堆栈视图与UIView
动画引擎兼容。这意味着动画排列的子视图的外观或消失的动画就像在动画块中切换其isHidden
属性一样简单。
更改已排列的子视图的isHidden
属性会更新其父堆栈视图的布局。如果该更新位于动画块中,则如果您在动画块中自己更改了布局,则它将是相同的。
是时候写一些代码了!打开SpotInfoViewController.swift
并查看updateWeatherInfoViews(hideWeatherInfo:animated :)
。
你会在方法的最后看到这些行:
weatherHideOrShowButton.setTitle(newButtonTitle, for: .normal)
weatherInfoLabel.hidden = shouldHideWeatherInfo
替换成下面的内容
if animated {
UIView.animate(withDuration: 0.3) {
self.weatherHideOrShowButton.setTitle(newButtonTitle, for: .normal)
self.weatherInfoLabel.isHidden = shouldHideWeatherInfo
}
} else {
weatherHideOrShowButton.setTitle(newButtonTitle, for: .normal)
weatherInfoLabel.isHidden = shouldHideWeatherInfo
}
构建并运行,然后点击Hide
或Show
按钮。 动画版本不是更好吗?
除了在堆栈视图中包含的视图上设置隐藏属性的动画之外,还可以在堆栈视图本身上设置动画属性,例如alignment, distribution, spacing
甚至是 axi
。
- 有关
Auto Layout
和使用UIStackViews
主题有几个WWDC视频。 来自WWDC 2017的Auto Layout Techniques in Interface Builder是一个很好的开始。 - 您还可以从Apple Documentation中阅读有关
UIStackView
的更多信息。
后记
本篇主要讲述了UIStackView的使用,感兴趣的给个赞或者关注~~~