介绍
UIViewController 可以理解为 App 的界面,负责管理 UIView 中显示的内容和用户的交互,主要有以下作用:
- 负责创建和管理 UIView。
- 响应用户与视图的交互。
- 负责界面的切换与传值。
- 响应设备的方向变化。
- 有一些特殊的视图控制器(导航控制器、标签栏控制器)可以更加方便和规范地管理 UIView。
创建
storyboard
- 初始化箭头指向的 UIViewController。
let vc = UIStoryboard(name: "storyboard名", bundle: nil).instantiateInitialViewController()
- 初始化其他的 UIViewController。
let vc = UIStoryboard(name: "storyboard名", bundle: nil).instantiateViewController(withIdentifier: "Storyboard ID")
纯代码
let vc = UIViewController()
xib
这种方式本质是 xib 创建 UIView,然后让这个 UIView 成为 UIViewController 的默认 View。
- 创建 UIViewController 的时候勾选了
Also create XIB file
,可以直接通过下面两种方式初始化:
// 方式一
let vc = UIViewController()
// 方式二
let vc = UIViewController(nibName: "xib的名字", bundle: nil)
- 如果 UIViewController 与 xib 分别创建,直接使用上面的两种方式会报错,因为这种方式还需要自己处理 2 件事:
(1)将 xib 文件 的File’s Owner
的类绑定为 UIViewController。
(2)将File’s Owner
的view
属性设置为xib
文件(拽线设置即可)。
view属性
在入门知识里初步介绍了 UIViewController 与其属性view
的关系,其实它们之间的关系没有那么简单,需要进一步分析。
生命周期顺序
init、init(nibName...)(初始化、分配内存)—> loadView(加载view)—> viewDidLoad(view已经加载)—> viewWillAppear(view即将显示)—> viewWillLayoutSubviews(将要布局子view)—> viewDidLayoutSubviews(已经布局子view)—> viewDidAppear(view已经显示)—> viewWillDisappear(view即将消失)—> viewDidDisappear(view已经消失)—> dealloc(释放内存)
延迟加载
UIViewController 的 view 的延迟加载:第一次使用的时候才会去加载,并不是创建 UIViewController 时加载。
- 验证:通过纯代码跳转时发现屏幕黑色且卡顿,设置颜色后正常。
loadView方法
- 用于创建 UIViewController 的 view。
- 当 UIViewController 访问 view 时如果发现为 nil,就会调用 loadView 方法。
- loadView 方法执行完会自动执行 viewDidLoad。
- loadView 方法大概的实现思路如下:
func loadView() {
// 如果UIViewController是通过storyboard创建的,从storyboard中加载视图来创建view
if storyboard创建 {
// ...
return
}
// 如果UIViewController是通过xib创建的,从xib中加载视图来创建view
if xib创建 {
// ...
return
}
// 如果上面都不是,则会创建一个普通的view视图
let view = UIView(frame: UIScreen.main.bounds)
self.view = view
}
重写loadView方法
- 该方法要么不重写,如果重写一定要注意:
- 必须在方法里给 UIViewController 的 view 赋值。
- 不要调用
super.loadView()
。 - 不要手动调用该方法。
override func loadView() {
let myView = UIView(frame: UIScreen.main.bounds)
view = myView
}
- 一旦重写,其他创建 view 的方式都会失效,因为决定 UIViewController 的 view 优先级为:loadView > storyboard > nibName > xib。
跳转
从一个 UIViewController 跳转到另一个 UIViewController 有两种方式,分别为模态跳转和导航跳转。
模态跳转
storyboard
- 直接拽线,选择
Present Modally
,这根线是一个 UIStoryboardSegue 对象(简称 Segue),可以设置相关的属性。 - 自动型 Segue
- 直接跳转,无需条件。
- 通过当前 UIViewController 某个具体的控件(如按钮)拽线到另一个 UIViewController。
- 手动型 Segue
- 从当前 UIViewController 拽线到另一个 UIViewController,需要给这根线设置
identifier
。 - 在程序中需要跳转的地方调用
performSegue(withIdentifier: , sender:)
方法完成跳转。
- 从当前 UIViewController 拽线到另一个 UIViewController,需要给这根线设置
纯代码
- 跳转界面:
present
。 - 返回界面:
dismiss
。 - iOS 13 之后,模态跳转并非全屏显示,如果需要全屏显示,需要手动设置。
两个概念
-
presentedViewController
: 被 present 的控制器。 -
presentingViewController
:正在 presenting 的控制器。
导航跳转
这种操作的前提是 UIViewController 包含在 UINavigationController 中。
storyboard
- 直接拽线,选择
Show
。 - 自动型 Segue 和 手动型 Segue 跟模态跳转一样。
纯代码
- 跳转界面
-
navigationController?.pushViewController
。
-
- 返回界面
- 左上角的返回按钮。
- 屏幕边缘滑动。
-
navigationController?.popViewController
。
传值
顺向传值
顺向传值即按照 UIViewController 跳转的顺序进行传值,比如控制器A跳转到控制器B,A向B的传值就是顺向传值。顺向传值只需要在目标控制器中声明需要接收的参数,然后在源控制器中进行传值即可。
- storyboard 方式。
- 代码方式。
逆向传值
逆向传值即按照 UIViewController 跳转的顺序反向进行传值,比如控制器A跳转到控制器B,控制器B在返回控制器A时进行传值,这种方式就是逆向传值。逆向传值不能像顺向传值那样简单进行,需要借助于下面三种方式。
代理
代理模式需要弄清楚被代理对象和代理对象,然后按照下面的规范进行。
- 被代理对象(需要传值的 UIViewController)
- 声明协议,在协议中定义传值方法,方法的参数个数与类型取决于需要传值的个数和类型。
- UIViewController 中声明一个代理属性。
- 在需要传值的地方调用代理属性的方法完成传值。
- 代理对象(接收值的 UIViewController)
- 实现被代理对象声明的协议,实现协议中的方法,拿到传过来的值进行使用。
- 需要设置当前的 UIViewController 为被代理 UIViewController 中的代理属性。
闭包
可以理解为代理模式中协议的闭包替代,比代理模式更简单。
- 需要传值的 UIViewController
- 声明一个闭包属性,闭包的参数个数与类型取决于需要传值的个数和类型,闭包的返回值一般为 Void。
- 在需要传值的地方调用闭包完成传值。
- 接收值的 UIViewController
- 实现需要传值的 UIViewController 中的闭包属性,在闭包的实现中拿到传过来的值进行使用。
通知
- 接收值的 UIViewController 通过监听通知捕获传过来的值。
NotificationCenter.default.addObserver(self, selector: #selector(handlerNoti), name: NSNotification.Name("abc"), object: nil)
- 需要传值的 UIViewController 将值通过通知的方式发送出去。
NotificationCenter.default.post(name: NSNotification.Name("abc"), object: nil, userInfo: ["info" : inputTf.text!])
- 需要先监听,后发送。
- iOS 9 之后 NSNotificationCenter 无需手动移除观察者。
常见ViewController
UIAlertController
- 警告(对话框)控制器。
- 用一个对话框进行信息的提示,通过模态形式弹出。
- 有两种样式:
alert
和actionSheet
。 - 按钮通过 UIAlertAction 添加,有 3 种样式:
default
、cancel
和destructive
,一个 UIAlertController 中只能有一个cancel
样式的 UIAlertAction。 - 基本使用
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func showAlert(_ sender: Any) {
let alertVC = UIAlertController(title: "温馨提示", message: "天气转凉,大家注意保暖,小心感冒", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default) { _ in
print("点击了ok")
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
print("点击了cancel")
}
alertVC.addAction(ok)
alertVC.addAction(cancel)
present(alertVC, animated: true, completion: nil)
}
@IBAction func showSheet(_ sender: Any) {
let alertVC = UIAlertController(title: "选择头像", message: "请选择合适的方式来处理", preferredStyle: .actionSheet)
let ok = UIAlertAction(title: "相册", style: .default) { _ in
print("用户选择了相册")
}
let des = UIAlertAction(title: "拍照", style: .destructive) { _ in
print("用户选择了拍照")
}
let cancel = UIAlertAction(title: "取消", style: .cancel) { _ in
print("点击了取消")
}
alertVC.addAction(ok)
alertVC.addAction(des)
alertVC.addAction(cancel)
present(alertVC, animated: true, completion: nil)
}
}
- 登录案例:用 UIAlertController 代替 print 打印。
UINavigationController
- 导航控制器。
- 可以展示多个 UIViewController,这些 UIViewController 是层级关系。
- 它的 View 由三部分组成,最上面的
UINavigationBar
,最下面默认隐藏的UIToolBar
,中间是 UIViewController 的view
。 - 通过栈管理 UIViewController:先进后出。
-
pushViewController
:压栈。 -
popViewController
:出栈。
-
- 通过 UINavigationItem 设置 title、leftBarButtonItem、rightBarButtonItem等。
UINavigationBar和UINavigationItem的关系
-
UINavigationBar
是 UINavigationController 的属性,其属性设置会影响内部所有的 UIViewController。 -
UINavigationItem
是 UIViewController 的属性,用于配置当前 UIViewController 显示时UINavigationBar
上显示的内容。 -
UINavigationBar
内部也维持一个栈,栈中存放的是一个个UINavigationItem
。当一个 UIViewController push 到 UINavigationController 时,它的UINavigationItem
也会被 push 进UINavigationBar
的栈。因此UINavigationBar
的栈和 UINavigationController 的栈一一对应。
UINavigationBar 的内容显示
标题
- 如果当前 UIViewController 设置了
titleView
属性,则展示标题视图。 - 如果当前 UIViewController 设置了
title
属性,则显示标题文字。 - 如果都没设置,则显示空白。
- iOS11 之后可以设置大标题。可以通过 storyboard 直接设置,也可以通过如下的代码设置:
// 所有界面显示大标题
navigationController?.navigationBar.prefersLargeTitles = true
// 当前界面是否显示大标题,never表示不显示大标题即显示小标题
navigationItem.largeTitleDisplayMode = .never
右侧按钮
- 如果当前 UIViewController 设置了
rightBarButtonItem
属性,则显示右侧按钮,否则显示空白。
左侧按钮
- 如果当前 UIViewController 设置了
leftBarButtonItem
属性,则显示左侧按钮。 - 如果前一个 UIViewController 设置了
backButtonItem
属性,则显示返回按钮。 - 如果前一个 UIViewController 设置了
title
属性,则显示标题文字封装的返回按钮。 - 如果以上都未设置,则展示文字
Back
封装的返回按钮。
注意:默认情况下返回按钮和左侧按钮是不同时显示的,只显示返回按钮而不显示左侧按钮。
返回按钮
- 如果当前 UIViewController 设置了
leftBarButtonItem
属性,则默认的返回按钮会被替代,自带的返回和从屏幕边缘滑动返回的效果失效,此时只能通过popViewController
返回。 - 如果前一个 UIViewController 设置了
backButtonItem
属性或设置了backButtonTitle
,可以起到更改返回按钮文字和图片的目的,但是返回按钮的<
图标会一直存在,这种方式自带的返回和从屏幕边缘滑动返回的效果依然有效。
颜色问题
- UINavigationBar 的颜色:可以通过 UINavigationBar 的
barTintColor
设置。 - UINavigationBar 上面内容的渲染颜色:默认情况下,按钮或系统图片按钮都会渲染成蓝色,可以通过 UINavigationBar 的
tintColor
设置。
案例
- storyboard 使用。
- 纯代码使用。
- 自定义使用。
UITabBarController
- 标签栏控制器。
- 可以展示多个 UIViewController,这些 UIViewController 是平级关系。但展示的 UIViewController 最多不超过5个,否则会折叠。
- 它的 View 由两部分组成,上面是 UIViewController 的
view
,下面是UITabBar
。 - 通过
addChildViewController
添加 UIViewController,通过UIViewController 的UITabBarItem
属性设置展示的文字、默认图片、选中图片和角标。 - 默认已经实现了
UITabBarDelegate
。
UITabBarControllerDelegate
- UITabBarController 还提供一个代理属性,通过它可以设置一个代理 UITabBarControllerDelegate。
- 监听切换 UIViewController
- 通过 UITabBarDelegate 的
tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem)
方法。 - 通过 UITabBarControllerDelegate 的
tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
方法。
- 通过 UITabBarDelegate 的
颜色问题
UITabBar的颜色
可以通过 UITabBar 的barTintColor
设置。
渲染颜色
- 图片一般由设计师统一设计,需要设置标题文字颜色以适应图片。
- 方式一:每个 UIViewController 单独设置。
// 默认文字颜色
vc.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.white], for: .normal)
// 选中文字颜色
vc.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.orange], for: .highlighted)
- 方式二:Appearance统一设置。
let item = UITabBarItem.appearance()
// 默认文字颜色
item.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.white], for: .normal)
// 选中文字颜色
item.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.orange], for: .highlighted)
- 方式三:iOS 10 之后可以统一设置选中和未选中颜色。(推荐使用)
// 选中的图片文字颜色
vc.tabBarController?.tabBar.tintColor = UIColor.orange
// 未选中的文字颜色
vc.tabBarController?.tabBar.unselectedItemTintColor = UIColor.white
// 角标的背景色
vc.tabBarItem.badgeColor = UIColor.orange
// 角标的颜色
vc.tabBarItem.badgeTextAttributes(for: .normal) = UIColor.white
案例
- storyboard 使用。
- 纯代码使用。
- 自定义使用。
其他
- UITableViewController:表视图控制器,集成了 UITableView 的视图控制器。
- UICollectionViewController:集合视图控制器,集成了 UICollectionView 的视图控制器。