Leaking views when changing rootViewController inside transitionWithView

前端 未结 5 1726
陌清茗
陌清茗 2020-12-04 05:29

While investigating a memory leak I discovered a problem related to the technique of calling setRootViewController: inside a transition animation block:

5条回答
  •  囚心锁ツ
    2020-12-04 06:09

    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 controller
    for (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 = kCATransitionFade
    window.set(rootViewController: viewController, withTransition: transition)
    

提交回复
热议问题