Why is `viewDidAppear` method called again on previous root view controller when swapping root view controller on window?

Viewed 429

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.

0 Answers
Related