Ios 在transitionWithView中更改rootViewController时泄漏视图
在调查内存泄漏时,我发现了一个与调用过渡动画块内的Ios 在transitionWithView中更改rootViewController时泄漏视图,ios,cocoa-touch,uiviewcontroller,core-animation,Ios,Cocoa Touch,Uiviewcontroller,Core Animation,在调查内存泄漏时,我发现了一个与调用过渡动画块内的setRootViewController:技术相关的问题: [UIView transitionWithView:self.window duration:0.5 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{ self.window.rootViewCon
setRootViewController:
技术相关的问题:
[UIView transitionWithView:self.window
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ self.window.rootViewController = newController; }
completion:nil];
如果旧视图控制器(被替换的视图控制器)当前正在显示另一个视图控制器,则上述代码不会从视图层次结构中删除显示的视图
也就是说,这一系列的操作
transitionWithView:
使Z成为新的根视图控制器UITransitionView
中。也就是说,经过上述三个步骤后,视图层次结构为:
- UIWindow
- 超转换视图
- UIView(Y视图)
- UIView(Z视图)
- 超转换视图
transitionWithView:
之前立即向X发送dismissViewControllerAnimated:NO
,则生成的视图层次结构为:
- UIWindow
- UIView(X的视图)
- UIView(Z视图)
dismissViewControllerAnimated:
(是或否)发送到X,然后在完成:
块中执行转换,则视图层次结构是正确的。不幸的是,这会干扰动画。如果将解雇动画化,则会浪费时间;如果不设置动画,则它看起来已损坏
我正在尝试其他一些方法(例如,创建一个新的容器视图控制器类作为我的根视图控制器),但没有找到任何有效的方法。我会随着时间的推移更新这个问题
最终目标是直接从显示的视图过渡到新的根视图控制器,而不留下分散的视图层次结构。我最近遇到了类似的问题。我必须手动从窗口中删除该
UITransitionView
,以解决问题,然后在上一个根视图控制器上调用dismise,以确保其解除分配
修复不是很好,但除非你在发布问题后找到更好的方法,否则这是我发现唯一有效的方法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版本的历史记录)
作为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)
或
我在使用此代码时遇到了这个问题:
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
})
}
禁用此代码,修复了问题。我只在初始化设置动画的过滤器栏时才启用此转换动画,从而成功地实现了这一点
这并不是你真正想要的答案,但它可以让你找到正确的解决方案。我面对这个问题,它让我烦恼了一整天。 我已经尝试过@Rich的obj-c解决方案,结果是,当我想在此之后展示另一个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上适用的简单方法:只需在解除ViewControllerAnimated
完成期间从其层次结构中删除旧的viewController视图
让我们使用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,他们的视图不再在层次结构中 也有类似的问题。在我的例子中,我有一个viewController层次结构,其中一个子视图控制器有一个显示的视图控制器。当我更改windows根视图控制器时,出于某种原因,显示的视图控制器仍在内存中。因此,解决方案是在我更改windows根视图控制器之前关闭所有视图控制器。我目前也遇到了同样的问题。我刚刚遇到了同样的问题。幸运的是找到了一个合适的解决方案吗?这里也有同样的问题。@DavidBaez我最后写了一段代码,在更改根目录之前,我会主动关闭所有视图控制器。不过,这对我的应用程序来说非常特殊。自从发布这篇文章以来,我一直想知道交换UIWindow
是否是应该做的事情,但还没有时间做很多实验。谢谢。成功了。如果您发现更好的方法,请分享。替换已显示视图的根视图控制器(或尝试解除锁定仍显示视图控制器的UIWindow)将导致内存泄漏。在我看来,显示视图控制器会创建一个带有窗口的保留循环,而取消控制器是我发现的唯一打破它的方法。我认为一些内部完成块对窗口有很强的引用。在转换为swift 2.0后,NSClassFromString(“UITransitionView”)出现问题在iOS 9中仍然存在:(我还更新了Swift 2。0@user023我已经在提交到应用商店的2到3个应用中使用了这个精确的解决方案,没有问题!我想,因为您只是根据字符串检查类的类型,所以这很好(可以是任何字符串)。可能导致拒绝的原因是在你的应用程序中有一个名为UITransitionView
的类,因为它是作为应用程序符号的一部分提取的,我认为应用程序
////
//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)