UIViewController — iOS应用程序的核心类,管理屏幕及其内容。iPhone或iPad的每个屏幕都由一个ViewController管理,它协调显示、生命周期和导航。关于UIKit架构的更多信息,请参阅Apple官方文档。
要点
UIViewController — UIKit框架中的一个类,管理UIView层次结构并协调数据在屏幕上的显示。每个iOS应用程序至少包含一个ViewController — 窗口的根控制器。控制器处理屏幕旋转、屏幕间的转场以及生命周期事件。
iOS中的MVC(Model-View-Controller)架构正是通过UIViewController实现的:控制器从模型接收数据并更新视图。ViewController不是可视元素 — 它管理包含subview层次结构的view属性。根据Apple的数据(2026年),UIKit包含超过40个内置的UIViewController子类。
第一个iPhone SDK(2008年)包含具有三个生命周期方法的UIViewController。在18年间,Apple增加了对Container View Controller的支持、自适应呈现、用于自定义动画的UIViewControllerTransitioningDelegate以及iPad上的分屏模式。UIViewController仍然是UIKit应用程序的必需组件。
UIViewController的生命周期 — 系统在创建、显示和隐藏屏幕时调用的方法序列。理解生命周期至关重要:代码放置不当会导致内存泄漏、不必要的网络请求和界面闪烁。
| 方法 | 调用时机 | 用途 |
|---|---|---|
| viewDidLoad | 一次,view加载到内存后 | 初始UI设置,Combine订阅 |
| viewWillAppear | 屏幕出现之前 | 数据更新,隐藏/显示导航栏 |
| viewDidAppear | 屏幕出现之后 | 启动动画,分析,更新相机 |
| viewWillDisappear | 离开屏幕之前 | 保存草稿,取消通知订阅 |
| viewDidDisappear | 离开屏幕之后 | 停止繁重进程,释放资源 |
首次显示屏幕时:init → loadView → viewDidLoad → viewWillAppear → viewDidAppear。重新出现时(从其他屏幕返回):viewWillAppear → viewDidAppear。viewDidLoad在控制器的生命周期中只调用一次。
viewDidLoad方法 — 用户界面配置的主要点。在view加载到内存后调用,此时所有IBOutlet都已连接。在这里以编程方式创建UI元素、配置约束、加载初始数据。
final class ProfileViewController: UIViewController {
private let tableView = UITableView()
private let viewModel = ProfileViewModel()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
bindViewModel()
}
private func setupUI() {
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
tableView.register(ProfileCell.self,
forCellReuseIdentifier: ProfileCell.reuseId)
}
private func bindViewModel() {
viewModel.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] user in
self?.title = user.name
}
.store(in: &cancellables)
}
}在SwiftUI中,此代码等同于View的主体。但UIViewController提供了对生命周期和优化的完全控制。bindViewModel使用Combine进行响应式订阅 — 数据在模型更改时自动更新。
viewWillAppear每次在屏幕出现之前调用,即使它已经在内存中。这是更新可能在其他屏幕上更改的数据的地方:重新加载列表、更新通知计数器、为特定屏幕配置导航栏。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 在此屏幕上隐藏导航栏
navigationController?.setNavigationBarHidden(true, animated: animated)
// 从其他屏幕返回时更新数据
tableView.reloadData()
badgeLabel.text = "\(CartManager.shared.itemCount)"
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 分析:仅在用户看到屏幕后
AnalyticsService.shared.logScreenView("Profile")
}viewDidLoad和viewWillAppear之间的区别至关重要:viewDidLoad执行一次,适用于静态配置;viewWillAppear每次显示时执行,适用于动态更新。将网络请求放在viewDidLoad中会导致返回屏幕时显示过时数据。
Container View Controller — 管理一个或多个子ViewController的ViewController。Apple提供三个内置容器:UINavigationController(屏幕堆栈)、UITabBarController(标签页)和UISplitViewController(iPad的master-detail)。
UINavigationController以堆栈形式组织转场 — push添加屏幕,pop移除。UITabBarController在应用程序的独立部分之间切换。UISplitViewController在iPad上并排显示两个控制器,在iPhone上显示一个。开发者可以通过addChild创建自己的容器。
// 自定义Container View Controller
final class ContainerViewController: UIViewController {
private let sidebarVC = SidebarViewController()
private let contentVC = ContentViewController()
override func viewDidLoad() {
super.viewDidLoad()
// 添加子控制器
addChild(sidebarVC)
view.addSubview(sidebarVC.view)
sidebarVC.didMove(toParent: self)
addChild(contentVC)
view.addSubview(contentVC.view)
contentVC.didMove(toParent: self)
}
}正确使用Container View Controller需要按此顺序调用addChild、添加view和didMove(toParent:)。移除时 — willMove(toParent: nil)、removeFromSuperview、removeFromParent。违反顺序会导致内存泄漏。
Massive View Controller的问题 — 当UIViewController包含数百行代码,包含业务逻辑、网络请求、导航和UI代码时。Apple意识到这个问题,并推荐MVVM(Model-View-ViewModel)结合Coordinator来分离导航。
MVVM将业务逻辑从控制器移到ViewModel。Controller仅通过Combine或delegate将ViewModel与View连接。Coordinator将导航逻辑 — 控制器的创建和转场 — 分离到单独的类中。这种方法自2024年起已被纳入Apple的最佳实践。
// Coordinator — 导航管理
protocol Coordinator {
var childCoordinators: [Coordinator] { get set }
func start()
}
final class MainCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
private let navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let vc = ListViewController()
vc.didSelectItem = { [weak self] item in
self?.showDetail(item)
}
navigationController.pushViewController(vc, animated: false)
}
private func showDetail(_ item: Item) {
let vc = DetailViewController(item: item)
navigationController.pushViewController(vc, animated: true)
}
}在UIViewController和SwiftUI View之间的选择取决于项目的开始年份、定制要求以及最低支持的iOS版本。带有UIViewController的UIKit仍然是2020年之前启动的项目和具有深度界面定制的应用程序的基础。
SwiftUI适用于iOS 17+的新项目、标准界面和原型。然而,对于自定义转场、相机操作、MapKit、复杂的CALayer动画,需要UIViewController。Apple建议通过UIHostingController(UIKit中的SwiftUI)和UIViewRepresentable(SwiftUI中的UIKit)结合两种方法。
| 场景 | UIKit (UIViewController) | SwiftUI (View) |
|---|---|---|
| 自定义动画 | 通过UIViewPropertyAnimator完全控制 | 通过Animation受限 |
| 相机操作 | AVCaptureSession + UIViewPreview | 通过UIViewControllerRepresentable |
| CollectionView | UICollectionView + UICollectionViewLayout | LazyVGrid/LazyHGrid |
| iPad适配 | UISplitViewController + UITraitCollection | NavigationSplitView + sizeClass |
| 开发速度 | 较慢(手动布局) | 较快(声明式) |
常见问题
UIViewController — 管理屏幕及其生命周期的控制器。UIView — 显示内容的视图。ViewController包含UIView层次结构,但其本身不是可视元素。一个控制器管理多个视图。
Massive View Controller — 一种反模式,当UIViewController包含太多逻辑:数据、导航、网络请求、动画。解决方案 — 将代码分离到单独的服务、协调器和ViewModel(MVVM)中。
四种方式:通过prepare(for:sender:)(Segue)时的属性、通过Delegate、通过Closure、通过共享服务。对于弱耦合,使用Coordinator + Delegate或Combine。
Container View Controller — 管理子ViewController的控制器。例如:UINavigationController、UITabBarController、UISplitViewController。父控制器通过addChild添加子控制器,在它们之间切换并管理它们的布局。
UIViewController — 用于复杂自定义动画、相机、地图、视频、具有自定义布局的UICollectionView。SwiftUI View — 用于iOS 13+的标准界面。允许通过UIHostingController进行组合。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。