Leaking views when changing rootViewController inside transitionWithView Leaking views when changing rootViewController inside transitionWithView ios ios

Leaking views when changing rootViewController inside transitionWithView


I had a similar issue recently. I had to manually remove that UITransitionView from the window to fix the problem, then call dismiss on the previous root view controller to ensure its deallocated.

The fix is not really very nice but unless you've found a better way since posting the question, its the only thing I've found to work! viewController is just the newController from your original question.

UIViewController *previousRootViewController = self.window.rootViewController;self.window.rootViewController = viewController;// Nasty hack to fix http://stackoverflow.com/questions/26763020/leaking-views-when-changing-rootviewcontroller-inside-transitionwithview// The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controllerfor (UIView *subview in self.window.subviews) {    if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {        [subview removeFromSuperview];    }}// Allow the view controller to be deallocated[previousRootViewController dismissViewControllerAnimated:NO completion:^{    // Remove the root view in case its still showing    [previousRootViewController.view removeFromSuperview];}];

I hope this helps you fix your problem too, it's an absolute pain in the arse!

Swift 3.0

(See edit history for other Swift versions)

For a nicer implementation as a extension on UIWindow allowing an optional transition to be passed in.

extension UIWindow {    /// Fix for http://stackoverflow.com/a/27153956/849645    func set(rootViewController newRootViewController: UIViewController, withTransition transition: CATransition? = nil) {        let previousViewController = rootViewController        if let transition = transition {            // Add the transition            layer.add(transition, forKey: kCATransition)        }        rootViewController = newRootViewController        // Update status bar appearance using the new view controllers appearance - animate if needed        if UIView.areAnimationsEnabled {            UIView.animate(withDuration: CATransaction.animationDuration()) {                newRootViewController.setNeedsStatusBarAppearanceUpdate()            }        } else {            newRootViewController.setNeedsStatusBarAppearanceUpdate()        }        if #available(iOS 13.0, *) {            // In iOS 13 we don't want to remove the transition view as it'll create a blank screen        } else {            // The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controller            if let transitionViewClass = NSClassFromString("UITransitionView") {                for subview in subviews where subview.isKind(of: transitionViewClass) {                    subview.removeFromSuperview()                }            }        }        if let previousViewController = previousViewController {            // Allow the view controller to be deallocated            previousViewController.dismiss(animated: false) {                // Remove the root view in case its still showing                previousViewController.view.removeFromSuperview()            }        }    }}

Usage:

window.set(rootViewController: viewController)

Or

let transition = CATransition()transition.type = kCATransitionFadewindow.set(rootViewController: viewController, withTransition: transition)


I faced this issue and it annoyed me for a whole day.I've tried @Rich's obj-c solution and it turns out when I want to present another viewController after that, I will be blocked with a blank UITransitionView.

Finally, I figured out this way and it worked for me.

- (void)setRootViewController:(UIViewController *)rootViewController {    // dismiss presented view controllers before switch rootViewController to avoid messed up view hierarchy, or even crash    UIViewController *presentedViewController = [self findPresentedViewControllerStartingFrom:self.window.rootViewController];    [self dismissPresentedViewController:presentedViewController completionBlock:^{        [self.window setRootViewController:rootViewController];    }];}- (void)dismissPresentedViewController:(UIViewController *)vc completionBlock:(void(^)())completionBlock {    // if vc is presented by other view controller, dismiss it.    if ([vc presentingViewController]) {        __block UIViewController* nextVC = vc.presentingViewController;        [vc dismissViewControllerAnimated:NO completion:^ {            // if the view controller which is presenting vc is also presented by other view controller, dismiss it            if ([nextVC presentingViewController]) {                [self dismissPresentedViewController:nextVC completionBlock:completionBlock];            } else {                if (completionBlock != nil) {                    completionBlock();                }            }        }];    } else {        if (completionBlock != nil) {            completionBlock();        }    }}+ (UIViewController *)findPresentedViewControllerStartingFrom:(UIViewController *)start {    if ([start isKindOfClass:[UINavigationController class]]) {        return [self findPresentedViewControllerStartingFrom:[(UINavigationController *)start topViewController]];    }    if ([start isKindOfClass:[UITabBarController class]]) {        return [self findPresentedViewControllerStartingFrom:[(UITabBarController *)start selectedViewController]];    }    if (start.presentedViewController == nil || start.presentedViewController.isBeingDismissed) {        return start;    }    return [self findPresentedViewControllerStartingFrom:start.presentedViewController];}

Alright, now all you have to do is call [self setRootViewController:newViewController]; when you want to switch root view controller.


I try a simple thing which work for me on iOs 9.3 : just remove the old viewController's view from its hierarchy during dismissViewControllerAnimated completion.

Let's work on X, Y, and Z view as explained by benzado :

That is, this sequence of operations...

  1. X becomes Root View Controller
  2. X presents Y, so that Y's view is on screen
  3. Using transitionWithView: to make Z the new Root View Controller

Which give :

//////Start point :let X = UIViewController ()let Y = UIViewController ()let Z = UIViewController ()window.rootViewController = XX.presentViewController (Y, animated:true, completion: nil)//////Transition :UIView.transitionWithView(window,                          duration: 0.25,                          options: UIViewAnimationOptions.TransitionFlipFromRight,                          animations: { () -> Void in                                X.dismissViewControllerAnimated(false, completion: {                                        X.view.removeFromSuperview()                                    })                                window.rootViewController = Z                           },                           completion: nil)

In my case, X and Y are well dealloc and their's view are no more in hierarchy !