UIStackView 可以方便地创建竖直或水平的布局,自动管理大部分约束。更重要的是 ,UIStackView 支待嵌套 ,这种灵活的方式可以创建出非常好看的界面。
打开 Main. storyboard , 再从对象库中拖一个新的 UIViewController 到画布上。最后拖一个竖直的 UIStackView 到 UIViewController 的 视图上。给 UIStackView 添加 一些约束,让它距离上下左右各 8 点 ;
下面拖四个 UILabel 到 UIStackView 上。从上到下,将它们的文字改为 “Name (名字)”、"Serial (序列号)”、" Value (价值)”和 “Date Created (创建时间)“;
- 现在马上出现了 一个问题:标签的边框都是红色的(表示有布局错误),并且有些界面有竖直布局不明确的警告。修复这个问题有两种方法:通过自动布局修复,或者使用 UIStackView 的属性修复 。首先使用自动布局修复。
隐藏的约束
- 在第 3 章中学习过,每个视图都有一个
内部内容大小
,如果没有明确指定宽度或高度,视图就会使用内部内容大小来决定宽度或高度 。 - 视图是使用隐藏的约束实现的。隐藏的约束是由视图的
内容变多优先级
和内容变少优先级
决定的。
内容变多优先级越高,越会保持视图不被拉伸。
内容变少优先级高的视图更能抵抗压缩,也就是不省略文字。
UIStack View 的分配
- 选择 Date Created (创建时间)标签,然后打开尺寸检视面板。找到
Vertical Content Hugging Priority
(竖直内容变多优先级),把它降低到 249 。现在其他三个标签有更高的优先级,因此它们会和内容大小一致 ,而 Date Created 会撑满剩下的空间。
嵌套的 UIStack View
- UIStackView 的一个强大特性是可以嵌套。接下来会在最大的 UIStackView 中嵌套几个 横向的 UIStackView,并且在上面的三个标签旁边各放置 一个 UITextField,用来展示 Item 对应的值,以及提供编辑功能。
选中画布上的 Name(名字)标签,点击自动布局菜单最后一个图标为“下载样式”的按钮 。这样会让选中的视图嵌套到 UIStackView 中 。
选中新的 UIStackView ,然后打开属性检视面板 。现在 UIStackView 是竖直方向的,但是这里需要 一个水平方向的。把 Axis (轴)属性的值改为 Horizontal (水平的)。
3.拖一个 UITextField 到 Name 标签的右边。默认情况下, UILabel 的内容变多优先级比 UITextField 的高,因此 UILabel 保持了内部内容的宽度,并且 UITextField 拉伸了。另一 方面,现在 UILabel 和 UITextField 有相同的内容变少优先级,当 UITextField 的文字太长时,就会出现布局不明确,打开 UITextField 的尺寸检视面板,把水平的 Content Compression Resistance Priority 设置为 749。
UIStack View 间距
现在 UILabel 和 UITextField 之间没有间隙,看起来非常拥挤 。 UIStackView 可以自定义元素之间的间距 。
- 选中水平的 UIStackView , 打开属性检视面板 ,将 Spacing ( 间距) 改为 8 点。 UITextField 为了适应间距而缩小了宽度,因为它的内容变少优先级比 UILabel 的低。
- 界面还需要调整 一些细节:为竖直的 UIStackView 设笠 一 定间距;将 Date Created 标签文字设置为居中;将 Name、 Serial 和 Value 二个标签的宽度设置为相同 。
- 选中竖直的 UIStackView , 打开属性检视面板,将 Spacing 设置为 8 点;选中 Date Created 标签,打开属性检视面板,将 Alignment (对齐)改为 Center (居中)。 这样前两个问题就解决了 。
- 虽然 UIStackView 大幅减少了界面上的约束,但有些约束仍然很重要 。 在当前界面中,由于标签的宽度不一样,所以 UITextField 的左侧并没有对齐 ( 英语中还不是很明显,如果本地化为其他语言,可能就更明显) 。 要解决这个问题,需要给三个 UITextField 设置开头约束 。
- 按住 Control,从名字 UITextField 拖到序列号 UITextField 上,再选择 Leading (开头)。然后对序列号 UITextField 和价值 UITextField 执行相同的操作 。
Segues
大部分 iOS 应用都有很多个 UIViewController , 用户可以在 UIViewController 之间切换 。 Storyboard 通过使用 segue, 不编写代码就可以实现这个交互。
- segue 可以把另一个 UIViewController 的视图加到屏幕上,它是一个 UIStoryboardSegue 对象 。 每个 segue 都有一个
样式 (style )
、操作对象 (action item)
和标识 (identifier)
。
样式:决定了 UIViewController 的展示方式;
操作对象:是 storyboard 文件中触发 segue 的视图对象,例如 UIButton 、 UITableViewCell 或者 UIControl ;
标识:是为了支持在代码中修改 segue, 这对于支持非视图控件触发的 segue 非常有用,例如可以通过摇晃手机触发,或者用 storyboard 无法添加的元素触发。
show seage (展示类型的 segue)
show segue 会根据要展示的内容来 决定如何展示 UIViewController 。
现在需要实现一个 UITableViewController 和新的UIViewController 之间的 segue, 操作对象是 UITableViewCell(ItemCell) , 点击新的 UITableViewCell会显示 UIViewController 。
-
在 Main. storyboard 中 选择 ItemsViewController 的 ItemCell 原型。按住 Control , 再拖到前面创建的 UIViewController 上,会出现选择 segue 样式的黑色菜单,选择 Show。
从 UITableViewController 指向新的 UIViewController 的箭头,就是 segue 。箭头中的图标表示 segue 的类型是 show(展示)。每种 segue 都有一个唯一的图标。
绑定内容
要显示选中 Item 的内容 ,需要再创建 一个 UIViewController 子类。创建一个叫 DetailViewController 的 Swift 文件。
- 设置关于UITextField插座变量。(使用助手编辑器操作)
-
打开 DetailViewController.swift 文件之后,按住 Option 再点击项目导航面板中的 Main. storyboard, 这样会在 DetailViewController. swift 旁边打开助手编辑器。
- DetailViewController.swift 需要保存将要显示的 Item , 当视图加载后,需要根据Item 对象给每个 UITextField 设置合适的值。
import UIKit
class DetailViewController: UIViewController {
@IBOutlet var nameField: UITextField!
@IBOutlet var serialNumberField: UITextField!
@IBOutlet var valueField: UITextField!
@IBOutlet var dateLabel: UILabel!
var item: Item! //添加一个 Item 属性
let numberFormatter: NumberFormatter = { //使用闭包来实例化数字格式化程序
let formatter = NumberFormatter()
formatter.numberStyle = .decimal //创建具有 .decimal 样式的 NumberFormatter,并将其配置为显示不超过2个小数位数。
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
nameField.text = item.name
serialNumberField.text = item.serialNumber
// valueField.text = "\(item.valueInDollars)"
// dateLabel.text = "\(item.dateCreated)"
valueField.text = numberFormatter.string(from: NSNumber(value: item.valueInDollars)) //调用 NumberFormatter方法 的固定格式,可以记住。
dateLabel.text = dateFormatter.string(from: item.dateCreated)
}
}
传递数据
当点击 UITableViewCell 之后,衙要 一种方式告诉 DetailViewController 选择了哪一个 Item 。
- 当 segue 触发时, UIViewController 的
prepare(for:sender: )
会被调用 ,因此可以在这时传递数据。这个方法有两个参数: 一个是 UIStoryboardSegue, 表示哪个 segue 被触发了;另 一个是 sender , 表示哪个对象触发了segue(例如 UITableViewCell 、 UIButton 等)。
- 从 UIStoryboardSegue 可以获得三个信息 : 源 UIViewControl ler , 目标 UIViewController, 以及 segue 的标识 。 标识可以用来区分 segue, 下面给 segue 设置一个标识。
- 打开 Main. storyboard , 点击两个 UIViewController 之间的箭头来选中 segue, 然后打开属性检视面板 。 在 Identifier (标识)中输入
Showltem
。
- 有了 segue 标识,就可以传递 Item 对象了。打开 ItemsViewController.swift, 实现prepare (for: sender:)。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.identifier { //这里用switch语句来判断可能出现的标识。
case "ShowItem"?: //由于segue的标识是可选字符串类型的,因此在case末尾有一个问号。
if let row = tableView.indexPathForSelectedRow?.row{ //找出点击了哪一行
let item = itemStore.allItems[row]
let detailViewController = segue.destination as! DetailViewController
detailViewController.item = item
}
default:
preconditionFailure("Unexpected segue identifier.")
//捕获不能处理的标识,让应用崩溃,这样可以防止在编写程序的过程中,忘记设置 segue 的标识,也可以防止标识的拼写错误。
}
}