0

If you swap out a rootViewController in UIWindow, for example, to present a log in window, if the rootViewController has presented (modal) view controllers, those presented view controllers and possibly the old rootViewController will not be deallocated.

self.window.rootViewController = newViewController
Marc Etcheverry
  • 1,010
  • 1
  • 10
  • 15

2 Answers2

2

UIKit objects are often released internally using autorelease. That means that they're not guaranteed to get released right away. You can, however, force things to deallocate by wrapping the function that should release the object with an autoreleasepool.

autoreleasepool {
    self.window.rootViewController = newViewController
}

For a good explanation of autorelease and autorelease pools, go check out this answer.

1

You must always do a dismissal of all modals when swapping out the rootViewController, otherwise there will be a retain cycle.

if window?.rootViewController.presentedViewController != nil {
    window?.rootViewController.dismiss?(animated: false) {
        window?.rootViewController = newViewController
    }
}
else {
    window?.rootViewController.dismiss?(animated: false)
    window?.rootViewController = newViewController
}

If you want to do a nice transition, you could always wrap it in an animation:

func transition() {
    if window?.rootViewController.presentedViewController != nil {
        window?.rootViewController.dismiss?(animated: false) {
            window?.rootViewController = newViewController
        }
    }
    else {
        window?.rootViewController.dismiss?(animated: false)
        window?.rootViewController = newViewController
    }
}

UIView.transition(with: window!,
                  duration: 0.3,
                  options: .transitionCrossDissolve,
                  animations: transition)
Marc Etcheverry
  • 1,010
  • 1
  • 10
  • 15
  • Even better, don’t do that. You shouldn’t change the root view controller. – matt Nov 21 '18 at 22:45
  • 1
    This is mostly right. For securely removal, here is one point : `self.myWindow?.rootViewController?.dismiss(animated: false, completion: { self.myWindow?.rootViewController = newViewController })` – E.Coms Nov 22 '18 at 02:01
  • @matt I see nothing in the documentation stating that changing the root view controller of a window is discouraged, it is a readwrite property. Much safer than dealing with existing view hierarchy in the existing root view controller or using view controller containment in a "base" root view controller. – Marc Etcheverry Nov 23 '18 at 03:15
  • @E.Coms Thank you, that is right! I assume completion is called even if there aren't any modals presented. Otherwise you would need a check on `presentedViewController`, I will do some testing later – Marc Etcheverry Nov 23 '18 at 03:17
  • I disagree obviously. – matt Nov 23 '18 at 03:44
  • @matt please provide justification so we can update the answer. Otherwise it is just a matter of opinion. Technically this is correct and safe as UIWindow objects support this despite them being somewhat ignored in iOS. You could also swap out windows themselves instead of adding complexity to your root view controller. – Marc Etcheverry Nov 23 '18 at 22:09
  • You don't have to do it my way, but as you've discovered there are issues when you mess with the view controller hierarchy from the bottom end like this. I'm saying you would have had those issues if you treated the view controller hierarchy the way it expects to be treated. You are giving a workaround for a self-inflicted wound. – matt Nov 24 '18 at 05:36
  • It isn’t a workaround. It is the proper way to dealloc a view controller manually. Many users swap out the root like this and I thought I would help them do it correctly. You should google “Compassionate Coding” it may be helpful in improving communication style. – Marc Etcheverry Nov 25 '18 at 06:34
  • @E.Coms I've tested it and the completion block does not get called if there isn't a presentedViewController, so I've updated my answer to safely work in all cases. – Marc Etcheverry Nov 26 '18 at 18:56