在更改transitionWithView内的rootViewController时泄漏视图
在调查内存泄漏时,我发现了一个与调用setRootViewController:
的技术有关的问题setRootViewController:
在一个过渡animation块中:
[UIView transitionWithView:self.window duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{ self.window.rootViewController = newController; } completion:nil];
如果旧视图控制器(被replace的视图控制器)当前正在呈现另一个视图控制器,则上述代码不会从视图层次结构中移除所呈现的视图。
就是这一系列的操作
- X成为根视图控制器
- X表示Y,所以Y的观点在屏幕上
- 使用
transitionWithView:
使Z成为新的根视图控制器
…对用户来说看起来没问题,但是Debug View Hierarchy工具将会在UITransitionView
显示Y的视图仍然存在于Z的视图UITransitionView
。 也就是说,在上述三个步骤之后,视图层次是:
- 的UIWindow
- UITransitionView
- UIView(Y的观点)
- UIView(Z的观点)
- UITransitionView
我怀疑这是一个问题,因为在转换时,X的视图实际上并不是视图层次结构的一部分。
如果在transitionWithView:
之前立即将dismissViewControllerAnimated:NO
发送到X,则生成的视图层次结构为:
- 的UIWindow
- UIView(X的视图)
- UIView(Z的观点)
如果我将dismissViewControllerAnimated:
(YES或NO)发送给X,则在completion:
块中执行转换,然后视图层次结构正确。 不幸的是,这干扰了animation。 如果animation解雇,浪费时间; 如果没有animation,它看起来破碎。
我正在尝试一些其他的方法(例如,使一个新的容器视图控制器类作为我的根视图控制器),但没有发现任何工作。 我会随时更新这个问题。
最终目标是直接从呈现的视图转换到新的根视图控制器,并且不会留下任何杂乱的视图层次结构。
我最近有类似的问题。 我不得不从窗口中手动删除该UITransitionView
来解决问题,然后在之前的根视图控制器上调用dismiss以确保其解除分配。
修复不是很好,但除非你发现问题后find更好的方法,否则我发现它是唯一的工作! 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]; }];
我希望这可以帮助你解决你的问题,这是一个绝对痛苦的屁股!
Swift 3.0
(请参阅其他Swift版本的编辑历史logging)
作为一个更好的实现作为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() } /// 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)
我面对这个问题,整整一天都让我烦恼。 我已经尝试了@ Rich的obj-c解决scheme,事实certificate,当我想在此之后呈现另一个viewController时,我将被一个空白的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];
当你想切换根视图控制器。
我尝试了一个简单的东西,在iOs 9.3上为我工作:在dismissViewControllerAnimated
完成期间,从其层次结构中删除旧的viewController的视图。
让我们按照benzado的解释来处理 X,Y和Z视图:
就是这一系列的操作
- X成为根视图控制器
- X表示Y,所以Y的观点在屏幕上
- 使用transitionWithView:使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是很好的dealloc,他们的观点不再是层次结构!
使用此代码时遇到此问题:
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 }) }
禁用此代码,解决了这个问题。 我设法得到这个工作,只有启用这个过渡animation,当获取animation的滤镜栏被初始化。
这不是你正在寻找的答案,但它可以把你find你的解决scheme的正确垫。