视图控制器

iOS Programming - 第五章 视图控制器

视图控制器是 UIViewController 的子类的一个实例。 视图控制器管理着视图层级。它负责创建组成视图层级的视图对象并在视图层级中处理跟视图对象关联的事件。

视图控制器中的 View


作为 UIViewController 的子类, 所有的视图控制器都继承了一个重要的属性:

1
var view: UIView!

这个属性指向一个 UIView 实例, 这个 view 是视图控制器的视图层级中的根视图。当视图控制器的 view 被作为 window 的子视图添加时, 该视图控制器的整个视图层级就被添加上了。

img

img

视图控制器的 view 直到它需要出现在屏幕上时才被创建。这种优化叫做懒加载(lazy loading), 这能减少内存使用并提升性能。

视图控制器有两种方式创建它的视图层级:

  • 编写程序, 通过重写 UIViewControllerloadView 方法
  • Interface Builder 中, 通过使用诸如 storyboard 的界面文件

在第三章中你已经使用了这两种方法, 在第六章中你将使用 loadView() 创建程序上的视图。

设置初始的视图控制器


每个 storyboard 都可以有很多视图控制器, 但是每个 storyboard 文件只有一个初始视图控制器(initial view controller)。 初始视图控制器是 storyboard 的入口点。你将在画布中添加并配置另外一个视图控制器并把该控制器设置为 storyboard 的初始视图控制器。

打开 Main.storyboard, 从对象库中, 拖拽一个 ViewController 到画布中, 如果你用完了可用的空间, 你可以按住 Ctrl 并单击背景视图来选择不同的缩放比例。

img

用这个视图控制器来显示 MKMapView。先选中这个 View Controllerview, 而不是 View Controller 自身! — 并按下 Delete 键来从画布中删除这个 view。 然后从对象库中拖拽一个 Map Kit View 到这个视图控制器中以把它设置为该视图控制器的 view

img

选中 View Controller 并打开它的属性检查器。勾选 Initial View Controller , 则该视图控制器的前面会出现一个灰色的箭头。

MKMapView 是一个当前未被加载到程序中的框架。框架是一个共享代码库, 里面包含诸如界面文件和图片等关联的资源。

现在你需要导入 MapKit 框架以加载 MKMapView。使用 import 关键字但不使用包含任何使用该框架的代码来导入 MapKit 会导致编译器把它优化掉 — 即使你在 storyboard 中使用 map view。

相反, 你需要手动地把 MapKit 链接到 app 中。

打开工程导航, 点击工程名。在设置中打开 General 标签, 滚动到最底部找到 Linked Frameworks and Libraries。 点击 + 号并搜索 MapKit.framework, 选中这个框架并点击 Add

img

UITabBarController


UITabBarController 保存了一个视图控制器的数组。UITabBarController 也在屏幕底部维护了一个 tab bar, 数组中每个视图控制器都带有一个 tab bar。触摸标签(tab)会呈现跟该标签(tab)相关联的视图控制器。

打开 Main.stroyboard 并选择 View Controller。 从 Editor 菜单中, 选择 Embed In -> Tab Bar Controller。 这会把 View Controller 添加到 Tab Bar Controller 的视图控制器数组中去。

按住 Control 键从 Tab Bar Controller 拖拽到 Conversion View Controller 上。在弹出的 Relationship Segue 一栏, 选择 view controllers

img

构建并运行该程序。此时标签上仅仅显示的文字是默认的 “Item”。在下一节, 你可以更新 tab bar items 以使 tabs 更具描述性。

UITabBarController 自身是一个 UIViewController 的子类。 UITabBarControllerview 是一个含有两个子视图的 UIView: 即 tab bar 和 所选择的视图控制器的 view

img

Tab bar items


tab bar 上的每个标签(tab)能展示一个标题(title)和图片(image), 为了这个目的, 每个视图控制器维护了一个 tabBarItem 属性。当视图控制器被包含在 UITabBarController 中时, 该视图控制器的 tab bar item 会出现在 tab bar 中。

img

Assets.xcassets 中拖入素材。

tab bar item 属性既能通过 storyboard 也能通过编程来设置。

在 storyboard 中, 定位到 View Controller。 注意, 当视图控制器将被呈现在 tab bar controller 时, 带有 tab bar itemtab bar 被添加到界面中。这在布局你的界面时会很有用。

选择这个 tab bar item 并打开它的属性检查器。 在 Bar Item 栏, 把 Title 设置为 “Map” 并从 Image 菜单中选择 MapIcon。你也可以在画布中双击文本来更改 tab bar item 的文本。

img

你还可以拖拽 tab bar item 以改变它们呈现的位置。

Loaded and Appearing Views


当程序启动时, tab bar controller 默认会加载它的数组中的第一个视图控制器的 view, 即 ConversionViewController。 这意味着 MapViewController 的 view 直到用户点击它的时候才被加载。

你可以自己测试这种懒加载行为。当视图控制器加载完它的视图后, 会调用 viewDidLoad() 方法, 你可以重写该方法以打印信息到控制台中。

你将为这两个视图控制器写代码。 然而, 展示 map 的视图控制器当前并没有代码与之关联, 因为所有的配置都是使用 storyboard。你需要创建一个视图控制器的子类并把它关联到界面中。

创建一个名为 MapViewController 的 UIViewController 子类:

1
2
3
4
5
import UIKit

class MapViewController: UIViewController {

}

打开 storyboard 并选中该 map 的视图控制器。打开它的身份检查器并把它的 Class 更改为 MapViewController

现在你把 MapViewController 类和画布中的视图控制器关联了起来, 你可以在 ConversionViewControllerMapViewController 中添加代码了。

在 ConversionViewController.swift 中重写 viewDidLoad() 方法,

1
2
3
4
5
override func viewDidLoad() {
// Always call the super implementation of viewDidLoad
super.viewDidLoad()
print("ConversionViewController loaded its view.")
}

在 MapViewController.swift 中重写同一个方法:

1
2
3
4
5
override func viewDidLoad() {
// Always call the super implementation of viewDidLoad
super.viewDidLoad()
print("MapViewController loaded its view.")
}

程序启动时就打印 ConversionViewController loaded its view, 当点击 Map Tab 时才打印 MapViewController loaded its view。并且都只会打印一次。 如果想执行多次某个事件, 应重写 viewWillAppearviewWillDisappear

访问子视图


通常, 在界面出现在用户面前之前, 你需要在 Interface Builder 中定义的子视图中做某些额外的初始化或配置。 所以你从哪里访问子视图? 有两种主要的观点, 取决于你需要干什么。一个地方是在 viewDidLoad() 方法中, 该方法在视图控制器的界面文件被加载之后调用, 这时视图控制器的所有 outlets 将会引用合适的对象。另外一个地方是在 viewWillAppear(_:) 中。这个方法在视图控制器的 view 被添加到 window 上之前被调用。

你应该选择哪个方法? 在程序运行期间, 如果配置只需要执行一次, 就重写 viewDidLoad() 。 如果需要在每次视图控制器的 view 出现在屏幕之前执行配置, 那么重写 viewWillAppear(_:) 方法。

跟视图控制器和它们的视图交互


让我们看看在视图控制器和它的视图的生命周期中所调用的方法。

  • init(coder: ) 是从 storyboard 创建的 UIViewController 实例的初始化函数。

当从 storyboard 创建视图控制器实例时, 会调用一次它的 init(coder:) 方法。在第 15 章中你会学到更多关于该方法的东西。

  • init(nibName:bundle: )UIViewController 的指定初始化构造函数。

当不使用 storyboard 创建视图控制器实例时, 会调用一次它的 init(nibName:bundle: ) 方法。注意在某些 app 中, 你可以创建几个同样的视图控制器类的实例。每创建一个视图控制器就调用一次该方法。

  • 重写 loadView() 以编程方式创建视图控制器的 view。
  • 重写 viewDidLoad 以配置通过加载界面文件创建的视图。该方法在视图控制器的 view 被创建之后调用。
  • 重写 viewWillAppear(_:) 以配置通过加载界面文件创建的视图

该方法和 viewDidAppear(_:) 会在每次视图控制器在移动到屏幕上时被调用。viewWillDisappear(_:)viewDidDisappear(_:) 会在每次视图控制器移出屏幕时调用。