UIWindow, rootViewController, visual artifacts and leaks.

Hello 👋

Today I am going to talk about UIWindowrootViewController and some of its quirks, namely how you can create leaks in your app by incorrectly using them.

My relationship with UIWindow is not that serious as the one with the WKWebKit, nevertheless we have some issues.

The task we tried to complete was straightforward, refactor the navigation between screens in an old application. Our approach was to start use Coordinators Pattern, and split the old codebase into multiple flows, each one responsible for a certain part of the application (Login, Feedback, Profile, etc.).

All this looked great on 📝 , so we started to implement it, needless to say that the codebase was poorly written and the navigation was splattered all over the view controllers in all sorts of bizarre extensions.

One common thing the old developers did, was manipulating the rootViewController of the UIWindow, and this one started creating us problems.

We all know that the refactoring process of an application is something which is time consuming and that the business doesn’t necessarily see the value in doing that, but thats another topic of discussion maybe for another post. What we tried to do was to implement the Coordinators Patterns only on certain flows of the application while leaving the rest of them to work as they did previously, meaning that we would have to make our flows work with the perviously existing UIWindow manipulation in place.

The problem wasn’t visible till we started presenting a view controller which had an alpha value of 0.5 which meant that it was transparent, and the view behind it was visible. That view controller was presented by replacing the root view controller of the current window.

let startupViewController = StartupViewController()
let navController = UINavigationController(rootViewController: startupViewController)
appDelegate.window.rootViewController = navController

What we have observed is the although the root view controller of the current window was changed, and the top view controller was the correct one (the transparent one) the old one was still visible under it.

Opening the UI view inspector did not help either, as both views were presented there as well. Our next step was trying to see who has a reference to that view controller and where is it displayed. We have started up the memory graph debug tool and we have found out that there are memory leaks, we had objects which weren’t deallocated as we expected them.

So, what happens?

We have tracked the issue to the way UIViewControllers were presented. If the previous rootViewController (or any of its children, or any other contained UIViewController if the root was a UINavigationController) had any UIViewControllers presented while changing the root of the window, those would not be deallocated and will still be visible.

What we end up doing in our project due to alpha on some view controllers and to they way screens were presented, was replacing the whole window with a new one and dismissing all the view controllers from the old one.

let newWindow = UIWindow(frame: UIScreen.main.bounds)
newWindow.rootViewController = rootViewController
UIWindow.transition(with: appDelegate.window, duration: 0.3, options: .transitionCrossDissolve, animations: {
    let oldWindow = appDelegate.window
    appDelegate.window = newWindow

    oldWindow?.rootViewController?.dismiss(animated: false, completion: nil)
  }, completion: nil)

Dismissing the calling dismiss on the root view controller will dismiss “its immediate child view controller and all view controllers above that child on the stack.

And on that bombshell, see you next time and till then make sure to call dismiss on the presented view controllers so that other developers who come after you won’t have to waste their time trying to comprehand what happens.