UIViewController 是 iOS 开发中非常核心的类,用来表示一个屏幕上的界面或页面。在App 中,每一个界面(比如设置页、详情页、登录页)背后其实都是一个 UIViewController 或其子类。
UIViewController 就是“页面”,控制着页面的视图、生命周期事件和交互行为。
简单来说:UIViewController 是一个控制器对象,它负责管理一块区域的用户界面(UI)和相关行为逻辑。
比如:
一个登录页是一个 LoginViewController
一个相册页面是一个 PhotoGalleryViewController
一个设置页是一个 SettingsViewController
可以把它理解为「页面控制中心」:在这个页面上看到的 UI 元素、交互逻辑,甚至跳转行为,全部都由这个控制器来负责。
一个 UIViewController 通常负责:
1、显示界面:加载并管理它的视图。
2、生命周期管理:控制视图的显示、消失、加载,如viewDidLoad()、viewWillAppear()等。
3、导航和切换界面:通过present()或navigationController.push()进行页面跳转。
4、响应用户交互:管理按钮、手势等事件响应。
5、和系统协作:如响应状态栏变化、深色模式、内存警告等。
管理内容
1、视图层级结构
它默认管理一个视图(view),可以往这个视图中添加按钮、图片、标签、表格等 UI 元素。
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "欢迎回来"
label.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
view.addSubview(label)
}
2、生命周期管理
UIViewController 生命周期是理解 iOS 开发中视图控制流程的关键。
以控制器从创建 ➝ 显示 ➝ 消失为例:
1、init(coder:) / init(nibName:bundle:):控制器被创建时调用,用于初始化变量,不涉及视图。
2、loadView():手动创建view树(如不适应storyboard),通常无需重写,除非想要自定义整个视图层级。
3、viewDidLoad():视图加载完毕,只执行一次,用于初始化视图、绑定数据、添加子视图。
4、viewWillAppear(_:):视图即将出现在屏幕上,每次出现前调用,用于准备UI(如刷新数据、控制导航栏显示)。
5、viewDidAppear(_:):视图已完全显示在屏幕上,启动动画、请求网络、开启监听。
6、viewWillDisappear(_:):视图即将被移除/覆盖,保存状态、关闭键盘、停止动画。
7、viewDidDisappear(_:):视图完全移除或被覆盖,释放资源、移除监听器。
8、deinit:控制器被销毁时,释放资源、取消通知等(只在非强引用循环下释放)。
3、用户交互与事件响应
它可以处理按钮点击、手势、通知、键盘事件等。
4、页面跳转(导航)
通过 present、dismiss 或 UINavigationController 实现页面切换。
let vc = ProfileViewController()
self.present(vc, animated: true)
或在导航控制器中:
navigationController?.pushViewController(vc, animated: true)
常用子类
UIViewController 是 iOS 应用中所有“页面”的基类,Apple 和开发者通常会通过其子类来实现特定类型的页面或行为。
1、UIViewController:基础控制器,最常用的基类,适合自定义任意页面。
2、UITableViewController:列表控制器,用于展示表格数据,比如设置页、联系人列表等。
3、UICollectionViewController:网格控制器,用于展示网格或卡片布局,比如相册、商城等。
4、UIPageViewController:分页控制器,用于左右滑动翻页,比如欢迎页、图文阅读器等。
5、UINavigationController:导航控制器,提供“返回”功能,管理页面的导航栈。
6、UITabBarController:标签栏控制器,底部有多个 tab 页的结构,如微信、微博首页结构。
7、UIAlertController:警告弹窗控制器,用于弹出警告、确认、选择等弹窗。
8、UISplitViewController:分栏控制器,iPad 上常用于主/从界面结构(左侧菜单 + 右侧内容)。
控制器切换方式
在 UIKit 中,UIViewController 提供多种方式来显示另一个控制器,包括但不限于:
1、present(_:animated:completion:):模态弹出一个控制器(全屏或部分屏),覆盖当前页面,适用于登陆、设置等页面。
let vc = SomeViewController()
present(vc, animated: true)
可以通过 override func present(…) 来拦截弹出行为。
override func present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)? = nil) {
// 拦截系统弹出行为
if viewControllerToPresent is UIAlertController {
print("拦截了系统弹窗")
return
}
super.present(viewControllerToPresent, animated: animated, completion: completion)
}
2、dismiss(animated:completion:):关闭当前模态控制器,返回上一个控制器(页面)。
dismiss(animated: true)
通常不重写,但是可以控制它何时触发,比如延迟、验证输入等。
3、navigationController?.pushViewController(_:animated:):使用导航控制器压栈(跳转到下一个控制器),适合练习浏览内容(比如新闻详情页)。
let detailVC = DetailViewController()
navigationController?.pushViewController(detailVC, animated: true)
拦截用法(一般在NavigationController的子类中):
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
print("将跳转到 \(viewController)")
super.pushViewController(viewController, animated: animated)
}
4、navigationController?.popViewController(animated:):从导航控制器中出栈(返回上一页)。
navigationController?.popViewController(animated: true)
通常不重写,但可以添加拦截逻辑,例如弹出确认框、保存草稿等。
5、addChild(_:) + view.addSubview(…):将一个控制器作为子控制器添加到当前控制器,比如分页标签控制器,每个标签是一个子控制器。
addChild(childVC)
view.addSubview(childVC.view)
childVC.didMove(toParent: self)
可以包装成组件化结构,但不建议重写系统方法,更多是组合使用。
6、transition(from:to:duration:options:animations:completion:):两个子控制器之间的切换(比如tab的切换),用于嵌套场景。
transition(from: currentVC, to: nextVC, duration: 0.3, options: [.transitionFlipFromLeft], animations: nil, completion: nil)
用于自定义容器控制器,适合自定义动画,但一般不重写,而是直接调用。
在SwiftUI中使用UIViewController
通常不会直接使用 UIViewController,而是会自己定义一个子类。
然后在 AppDelegate/SceneDelegate 里设置它为根控制器,或者在当前控制器中使用 present 或 push 跳转它。
1、UIViewController生命周期示例
下面将在Swift中展示UIViewController生命周期。
1、创建UIViewController子类代码:
class DemoViewController: UIViewController {
override func loadView() {
super.loadView()
print("loadView")
}
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("viewWillDisappear")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("viewDidDisappear")
}
deinit {
print("deinit called - ViewController 被释放")
}
}
2、SwiftUI包装器:
struct DemoViewControllerWrapper: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> DemoViewController {
return DemoViewController()
}
func updateUIViewController(_ uiViewController: DemoViewController, context: Context) {
// 不需要处理更新
}
}
3、在SwiftUI中使用:
struct ContentView: View {
var body: some View {
DemoViewControllerWrapper()
.edgesIgnoringSafeArea(.all)
}
}
Xcode在进入视图后,输出:
loadView
viewDidLoad
viewWillAppear
viewDidAppear
与生命周期中的loadView()、viewDidLoad()、viewWillAppear(_:)和viewDidAppear(_:)对应。
2、UIViewController拦截提示框示例
我们可以仿照《惊人开发技巧:轻松更换 App 图标,无需系统弹窗!》一文中的拦截弹窗代码,在SwiftUI中使用UIViewController 并拦截 present 行为。
通过 SwiftUI 的 UIViewControllerRepresentable 来包装 UIViewController,并展示如何通过重写 present(_:animated:completion:) 拦截弹窗行为(比如系统的 UIAlertController 弹窗)。
实现效果:在SwiftUI中点击按钮,尝试弹出一个UIAlertController,但是这个弹窗会被拦截,不会真正显示出来。
1、创建拦截控制器
import UIKit
class InterceptingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let button = UIButton(type: .system)
button.setTitle("尝试弹出 Alert", for: .normal)
button.addTarget(self, action: #selector(showAlert), for: .touchUpInside)
button.tintColor = .red
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
@objc func showAlert() {
let alert = UIAlertController(title: "警告", message: "你不该看到我!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "好", style: .default))
present(alert, animated: true)
}
// ✅ 拦截弹出行为
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if viewControllerToPresent is UIAlertController {
print("⚠️ 被拦截了 UIAlertController,不弹出")
return
}
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
}
2、SwiftUI 包装器
import SwiftUI
struct InterceptingViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> InterceptingViewController {
return InterceptingViewController()
}
func updateUIViewController(_ uiViewController: InterceptingViewController, context: Context) {
// 不需要更新内容
}
}
3、在SwiftUI中使用
import SwiftUI
struct ContentView: View {
var body: some View {
InterceptingViewControllerRepresentable()
.edgesIgnoringSafeArea(.all)
}
}
在SwiftUI中可以看到“尝试弹出 Alert”的按钮。

当点击“尝试弹出 Alert”按钮时,Xcode会输出:
⚠️ 被拦截了 UIAlertController,不弹出
并且,Xcode视图中不会弹出警告框。
当隐藏重写的present代码后,
// override func present() {} // 隐藏重写的present
就会看到Xcode弹出的警告框:

和SwiftUI区别
SwiftUI 没有 UIViewController,它是声明式编程,使用 View 来替代控制器和视图两层结构。
但底层运行时 SwiftUI 其实仍然使用 UIViewController 来渲染页面,可以通过 UIHostingController 把 SwiftUI 嵌入到 UIKit 中。
总结
UIViewController 是每个页面的“控制中心”,它配合 view 来显示界面、处理交互。在 App 中跳转、弹窗、导航几乎都绕不开它。
本文简单的讲解了生命周期和部分控制器方法。但是UIViewController涉及的内容其实很广:
1、生命周期:控制器从创建、显示到销毁的过程。
2、控制器切换:present/dismiss、push/pop、child controller 管理。
3、视图控制:view、viewDidLoad()、布局、子视图管理。
4、容器控制:UINavigationController、UITabBarController、UIPageViewController 等。
5、状态栏、屏幕方向控制:状态栏样式、横竖屏控制等。
6、内存警告管理:didReceiveMemoryWarning()。
7、动画与过渡:自定义转场动画、转场代理。
8、响应事件:手势、按钮点击、Input 响应链管理。
9、SwiftUI 互操作:通过 UIViewControllerRepresentable 嵌入 SwiftUI。
相关文章
1、Swift的桥接类型NSString:https://fangjunyu.com/2025/04/07/swift%e7%9a%84%e6%a1%a5%e6%8e%a5%e7%b1%bb%e5%9e%8bnsstring/
2、惊人开发技巧:轻松更换 App 图标,无需系统弹窗!:https://mp.weixin.qq.com/s/-wGkKPRTz7aYvMxsN3PcFg
扩展知识
SwiftUI包装器
为什么需要创建SwiftUI包装器来调用UIViewController?
因为SwiftUI和UIKit是两个不同的 UI 框架
SwiftUI 是苹果2019年推出的新声明式 UI 框架。
UIKit 是从 iOS 2 就开始使用的老牌 UI 框架,控制器以 UIViewController 为核心。
两者之间架构完全不同,SwiftUI 不能直接管理 UIKit 的控制器。
所以,SwiftUI 想要使用 UIKit 的东西(如 UIViewController),就需要一个桥接器(桥梁)。
这个桥接器就是:
UIViewControllerRepresentable
它是一个 SwiftUI 协议,允许任何一个 UIViewController 以 SwiftUI 组件的方式嵌入 SwiftUI 界面中。
是如何调用的?(底层工作原理)
创建一个结构体实现 UIViewControllerRepresentable 协议,它必须实现两个函数:
func makeUIViewController(context: Context) -> UIViewController
func updateUIViewController(_: UIViewController, context: Context)
1、makeUIViewController:当 SwiftUI 界面第一次显示时调用,返回要嵌入的 UIKit 控制器。可以在这里返回 DemoViewController()。
2、updateUIViewController:当 SwiftUI 状态更新时调用。可以在这里同步数据更新,比如状态驱动刷新。
在SwiftUI底层:
1、调用 makeUIViewController,拿到UIViewController。
2、将它放进一个SwiftUI容器里展示出来。
3、当视图销毁时,它会自动回收这个控制器。
在SwiftUI中添加对应输出,并结合在生命周期的代码:
struct DemoViewControllerWrapper:UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> DemoViewController {
print("进入 makeUIViewController 方法")
return DemoViewController()
}
func updateUIViewController(_ uiViewController: DemoViewController , context: Context) {
print("进入 updateUIViewController 方法")
// 不需要处理更新
}
}
struct ContentView: View {
var body: some View {
DemoViewControllerWrapper()
.edgesIgnoringSafeArea(.all)
}
}
输出的内容为:
进入 makeUIViewController 方法
loadView
viewDidLoad
进入 updateUIViewController 方法
viewWillAppear
viewDidAppear
1、在SwiftUI桥接机制的入口,首先打印了makeUIViewController的输出。
SwiftUI创建了UIViewController实例,比如DemoViewController()。
2、系统自动调用UIViewController生命周期的loadView和viewDidLoad。
当返回控制器后,UIKit系统会自动:
1)调用loadView:加载视图层级。
2)调用viewDidLoad:视图已经加载完毕,可以做初始化。
3、SwiftUI继续调用updateUIViewController方法。
SwiftUI 在首次渲染和状态更新时都会调用这个方法来同步状态。
这是一个“同步点”,并不直接触发任何生命周期方法。
4、控制器即将显示,UIKit自动调用,
控制器开始真正出现在屏幕上时,UIKit继续走标准流程:
1)viewWillAppear:即将出现在屏幕上。
2)viewDidAppear:已经出现。
