In a project a root view controller is being swapped at some point to recreate a whole window hierarchy. A pretty standard code is called simply using
windows.rootViewController = newController
but this code instantly calls viewDidAppear on the view controller that is about to be destroyed. And it is only called on root view controller. The method viewDidAppear is correctly called after a while on newController but why would it be called on the previous view controller?
To extract the issue I created a new project and added minimum amount of code that produces the issue. So if you create a new project and just modify your autogenerated ViewController.swift to following you should be able to produce the issue:
class ViewController: UIViewController {
static var loadInstance: Int = 0
var instance: Int = -1
override func viewDidLoad() {
super.viewDidLoad()
ViewController.loadInstance += 1
self.instance = ViewController.loadInstance
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("Calling did appear on instance: \(instance)")
let controller = AnotherController()
controller.modalPresentationStyle = .fullScreen
controller.view.backgroundColor = UIColor.red
present(controller, animated: true, completion: nil)
}
}
class AnotherController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
}
@objc private func onTap() {
print("Swapping root")
UIApplication.shared.windows.first?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!
}
}
The root view controller needs to have presented at least one other view controller for the issue to work. It needs to be as full screen as well.
In this example as you press your screen a new controller will be created which swaps the root and presents a new red view controller. Each time you do so you can see the log that the previous root controller reports viewDidAppear being called. The log looks like the following:
Calling did appear on instance: 1
Swapping root
Calling did appear on instance: 1
Calling did appear on instance: 2
Swapping root
Calling did appear on instance: 2
Calling did appear on instance: 3
Simply removing full screen setting produces expected result:
Calling did appear on instance: 1
Swapping root
Calling did appear on instance: 2
Swapping root
Calling did appear on instance: 3
Is there a reason for this or is it just a bug? In any case: Is there a solution to go around it and still safely use viewDidAppear for root controller?
Note that this is just a minimal presentation of the issue. The actual application is a bit more complex.