viewDidLoad — 是UIKit在将UIViewController的View加载到内存后调用的第一个方法。根据Apple Developer Documentation,这个方法在控制器的整个生命周期中只被调用一次。viewDidLoad是进行界面初始配置、注册单元格和初始化数据的主要位置。
要点
viewDidLoad — 是UIViewController的一个实例方法,UIKit在控制器的View加载到RAM后立即调用它。此时,所有IBOutlet属性已经连接到界面元素,但View尚未添加到窗口层次结构中,对用户不可见。开发者重写此方法以执行屏幕的初始配置。
该方法是ViewController Lifecycle的一部分,如果View是以编程方式创建的,则紧随loadView之后,或者从Storyboard加载后立即执行。在典型项目中,viewDidLoad是UIViewController最常被重写的方法,因为它提供了一个安全点来操作已经存在且准备配置的subviews。
一个重要细节:在调用viewDidLoad时,View的尺寸尚不符合最终尺寸——Auto Layout尚未完成传递,frame可能与预期不同。对于依赖于尺寸的计算,应使用viewDidLayoutSubviews。
viewDidLoad的调用时机取决于控制器的初始化方式。在大多数情况下,UIKit在第一次访问控制器的view属性时自动调用此方法——这称为UIViewController的lazy-loading机制。
当NavigationController或TabBarController首次显示您的屏幕时,UIKit检查View是否已加载。如果没有——则调用loadView(或从Storyboard加载),之后立即触发viewDidLoad。这是标准场景,每个控制器实例只发生一次。
override func viewDidLoad() {
super.viewDidLoad()
print("View已加载——可以配置界面")
setupUI()
configureTableView()
}
viewDidLoad不会再次调用通过back按钮或dismiss返回屏幕时。如果您的逻辑依赖于屏幕再次出现——请将其放在viewWillAppear中。这是最常见的概念错误之一:开发者期望viewDidLoad在每次显示时触发,但UIKit只调用它一次。
有时开发者强制调用控制器的view以提前启动加载:let _ = controller.view。这强制在控制器出现在屏幕之前调用loadView和viewDidLoad。当需要提前准备View以实现平滑过渡时,会使用这种技巧。
viewDidLoad旨在执行不依赖于屏幕是否可见的一次性配置操作。正确使用此方法是实现清晰架构和控制器可预测行为的关键。
在viewDidLoad中注册UITableView和UICollectionView的nib文件和类,配置委托,设置UI元素属性的初始值。由于此时所有IBOutlet都已连接,可以安全地访问label.text、imageView.image和其他subviews属性。
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(
CustomCell.self,
forCellReuseIdentifier: CustomCell.identifier
)
title = "主屏幕"
}
在这里创建viewModel,用数组初始化data source,订阅应在控制器整个生命周期内运行的通知。例如,订阅UIApplication.willEnterForegroundNotification以便在从后台返回时更新数据——这是viewDidLoad的合适候选。ViewModel在现代iOS架构中充当控制器和业务逻辑之间的桥梁,在viewDidLoad中初始化它可确保在屏幕首次出现时数据已经准备就绪。
特别关注表格和集合的data source配置。如果您的表格使用Core Data的UIFetchedResultsController或NSFetchedResultsController,请在viewDidLoad中初始化fetch request和委托。这保证了在屏幕首次出现时,表格无需额外查询就已填充数据。
在viewDidLoad中配置NavigationBar按钮、设置large title、添加search controller和设置edit/done按钮。这些元素在屏幕重复显示时很少更改,因此在此处初始化它们是最优的。
并非所有操作都适合在viewDidLoad中执行。放置在此方法中的某些操作会导致内存过度消耗、行为异常或屏幕重复显示时的错误。
避免启动仅影响UI的网络请求。如果请求在屏幕出现之前完成,用户将看不到结果,如果在之后完成——数据可能已过时。在viewDidLoad中启动加载,但在viewWillAppear中更新UI。
不要在viewDidLoad中执行依赖于View尺寸和位置的操作。在调用时,Auto Layout尚未完成传递,frame可能不是最终的。对于计算,请使用viewDidLayoutSubviews或重写updateViewConstraints。
不要订阅仅在屏幕可见时才起作用的通知。键盘通知、子控制器内容变更通知——在viewWillAppear中订阅并在viewDidDisappear中取消订阅,以避免不必要的调用和内存泄漏。
不要调用需要可见屏幕的方法。例如,从viewDidLoad尝试显示UIAlertController将导致错误,因为控制器的View尚未添加到窗口层次结构。所有依赖于window或presentedViewController的UI操作只能在屏幕出现后执行。
不要初始化不必要的重量级资源。如果屏幕很少打开或数据不立即显示,请将创建资源密集型对象推迟到真正需要时。Swift中的属性懒初始化是解决此任务的内置机制:带有lazy修饰符的属性仅在第一次访问时才创建,从而节省内存并加速屏幕加载。
不要使用viewDidLoad执行应在每次屏幕显示时执行的操作。这是最基本的错误:初级开发者经常将数据更新逻辑放在viewDidLoad中,然后奇怪为什么从其他屏幕返回时表格没有重新加载。如果操作应在每次显示时重复——使用viewWillAppear。如果在生命周期内只应执行一次——使用viewDidLoad。记住这个简单规则,以避免UIViewController生命周期相关的大部分问题。
让我们来看三个实际示例,展示viewDidLoad在真实项目中的正确使用。每个示例解决一个具体的屏幕配置任务。
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(
PhotoCell.self,
forCellWithReuseIdentifier: PhotoCell.reuseId
)
collectionView.register(
HeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: HeaderView.reuseId
)
viewModel.delegate = self
viewModel.fetchInitialPage()
}
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "你好,世界!"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
在viewDidLoad中还配置了在无数据时显示的元素:空状态、加载器、占位符。这些组件创建一次并在每次屏幕显示时重复使用。这些元素的隐藏或显示根据当前数据在viewWillAppear中控制。
override func viewDidLoad() {
super.viewDidLoad()
emptyStateLabel = UILabel()
emptyStateLabel.text = "没有数据"
emptyStateLabel.textAlignment = .center
emptyStateLabel.isHidden = true
view.addSubview(emptyStateLabel)
activityIndicator = UIActivityIndicatorView(style: .medium)
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
}
@objc private func handleEnterForeground() {
refreshContent()
}
常见问题
在正常情况下不会——UIKit在View加载到内存后调用一次viewDidLoad。如果控制器被销毁并重新创建,viewDidLoad将为新实例执行。
是的,必须调用。调用super.viewDidLoad确保UIKit执行正确运行Lifecycle所需的内部配置。始终在方法中首先调用super。
viewDidLoad在View加载时调用一次。viewWillAppear在每次屏幕出现之前调用。前者用于一次性配置,后者用于更新数据和状态。
viewDidLoad中的重量级同步操作会阻塞主线程并延迟屏幕出现。异步加载是允许的,但在完成更新UI时需要注意屏幕可能已经隐藏。
不能直接调用viewDidLoad——它由UIKit调用。要强制加载View,请访问controller.view属性。这将自动触发loadView和viewDidLoad。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。