ios 在transitionWithView内部更改rootViewController时泄漏视图

oyxsuwqo  于 2022-12-24  发布在  iOS
关注(0)|答案(5)|浏览(141)

在调查内存泄漏时,我发现了一个与在过渡动画块内调用setRootViewController:的技术相关的问题:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newController; }
                completion:nil];

如果旧的视图控制器(被替换的视图控制器)当前正在呈现另一个视图控制器,那么上面的代码不会从视图层次结构中删除所呈现的视图。
也就是说,这一系列的操作...

  1. X成为根视图控制器
  2. X代表Y,因此Y的视图显示在屏幕上
    1.使用transitionWithView:使Z成为新的根视图控制器
    ......在用户看来没有问题,但是Debug View Hierarchy工具将显示Y的视图仍然在Z的视图后面,在UITransitionView中。也就是说,在上述三个步骤之后,视图层次为:
  • UI窗口
  • UI转换视图
  • UI视图(Y的视图)
  • UI视图(Z视图)

我怀疑这是一个问题,因为在转换时,X的视图实际上并不是视图层次结构的一部分。
如果我在transitionWithView:之前发送dismissViewControllerAnimated:NO到X,则得到的视图层次结构为:

  • UI窗口
  • UI视图(X的视图)
  • UI视图(Z视图)

如果我发送dismissViewControllerAnimated:(YES或NO)到X,然后在completion:块中执行转换,那么视图层次是正确的,不幸的是,这会干扰动画,如果动画消除,会浪费时间;如果不设置动画,它看起来会损坏。
我尝试了一些其他的方法(例如,创建一个新的容器视图控制器类作为我的根视图控制器),但是没有找到任何有效的方法。
最终目标是直接从当前视图转换到新的根视图控制器,而不留下分散的视图层次结构。

kmynzznz

kmynzznz1#

有一个类似的问题。在我的例子中,我有一个视图控制器层次结构,其中一个子视图控制器有一个显示视图控制器。当我改变windows根视图控制器时,由于某种原因,显示视图控制器仍然在内存中。所以,解决方案是在我改变windows根视图控制器之前,先关闭所有视图控制器。

yc0p9oo0

yc0p9oo02#

我在使用此代码时遇到此问题:

if var tc = self.transitionCoordinator() {

    var animation = tc.animateAlongsideTransitionInView((self.navigationController as VDLNavigationController).filtersVCContainerView, animation: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in
        var toVC = tc.viewControllerForKey(UITransitionContextToViewControllerKey) as BaseViewController
        (self.navigationController as VDLNavigationController).setFilterBarHiddenWithInteractivity(!toVC.filterable(), animated: true, interactive: true)
    }, completion: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in

    })
}

禁用这段代码,修复了这个问题。我设法让这个工作,只有启用这个过渡动画时,得到动画的过滤器条是初始化。
这并不是你真正要寻找的答案,但它可以为你找到正确的解决方案。

yvt65v4c

yvt65v4c3#

我最近遇到了一个类似的问题。我不得不手动从窗口中删除那个UITransitionView来修复这个问题,然后在以前的根视图控制器上调用dismiss来确保它的解除分配。
修复不是真的很好,但除非你找到了一个更好的方法,因为张贴的问题,这是唯一的事情,我发现工作!viewController只是newController从你原来的问题。

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];
}];

雨燕3.0

(See其他Swift版本的编辑历史记录)
作为UIWindow上的一个扩展,允许传入一个可选的转换,这是一个更好的实现。

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()
            }
        }
    }
}

用法:

window.set(rootViewController: viewController)

或者

let transition = CATransition()
transition.type = kCATransitionFade
window.set(rootViewController: viewController, withTransition: transition)
xnifntxz

xnifntxz4#

这个问题困扰了我一整天,我尝试了@Rich的obj-c解决方案,结果发现当我想再显示一个视图控制器时,会被一个空的UITransitionView阻塞。
最后,我想出了这个办法,它对我很有效。

- (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];
}

好了,现在您要做的就是在切换根视图控制器时调用[self setRootViewController:newViewController];

83qze16e

83qze16e5#

我尝试了一个简单的事情,为我工作的iOs 9.3:只需在dismissViewControllerAnimated完成期间从层次结构中删除旧viewController的视图。
让我们按照benzado的说明处理X、Y和Z视图:
也就是说,这一系列的操作...

  1. X成为根视图控制器
  2. X代表Y,因此Y的视图显示在屏幕上
    1.使用带有视图的过渡:使Z成为新的根视图控制器
    给予:
////
//Start point :

let X = UIViewController ()
let Y = UIViewController ()
let Z = UIViewController ()

window.rootViewController = X
X.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)

在我的例子中,X和Y都很好地解除了分配,它们的视图不再处于层次结构中!

相关问题