メモリリークの調査中に、遷移アニメーションブロック内でsetRootViewController:
を呼び出す手法に関連する問題を発見しました。
[UIView transitionWithView:self.window
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ self.window.rootViewController = newController; }
completion:nil];
古いView Controller(置き換えられるもの)が現在別のView Controllerを提示している場合、上記のコードは提示されたビューをView階層から削除しません。
つまり、この一連の操作...
transitionWithView:
を使用してZを新しいルートビューコントローラーにする...ユーザーには問題ないように見えますが、Debug View Hierarchyツールは、YのビューがUITransitionView
内でZのビューの後ろにまだあることを明らかにします。つまり、上記の3つのステップの後、ビュー階層は次のようになります。
移行の時点では、Xのビューは実際にはビュー階層の一部ではないため、これは問題だと思われます。
dismissViewControllerAnimated:NO
の直前にtransitionWithView:
をXに送信すると、結果のビュー階層は次のようになります。
dismissViewControllerAnimated:
(YESまたはNO)をXに送信し、completion:
ブロックで遷移を実行すると、ビュー階層が正しくなります。残念ながら、それはアニメーションに干渉します。解雇をアニメーション化すると、時間を無駄にします。アニメートしていない場合、壊れているように見えます。
私は他のいくつかのアプローチ(たとえば、ルートビューコントローラとして機能する新しいコンテナビューコントローラクラスを作成する)を試していますが、機能するものは見つかりませんでした。この質問は、今後更新します。
最終的な目標は、表示されたビューから新しいルートビューコントローラーに、移行するビュー階層を残さずに直接移行することです。
最近、同様の問題が発生しました。この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];
}];
これがあなたの問題を解決するのに役立つことを願っています、それはひどい痛みです!
Swift 3.
(他の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()
}
/// 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)
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されており、それらのビューは階層構造ではありません!
私はこの問題に直面し、それが一日中私を悩ませました。 @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];
ルートビューコントローラを切り替えたい場合。