ios - Vistas fugaces al cambiar de rootViewController dentro de transitionWithView
cocoa-touch uiviewcontroller (4)
Mientras investigaba una pérdida de memoria, descubrí un problema relacionado con la técnica de llamar a setRootViewController:
dentro de un bloque de animación de transición:
[UIView transitionWithView:self.window
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ self.window.rootViewController = newController; }
completion:nil];
Si el controlador de vista anterior (el que está siendo reemplazado) presenta actualmente otro controlador de vista, entonces el código anterior no elimina la vista presentada de la jerarquía de vista.
Es decir, esta secuencia de operaciones ...
- X se convierte en controlador de vista raíz
- X presenta Y, por lo que la vista de Y está en la pantalla
- Usando
transitionWithView:
para hacer que Z sea el nuevo controlador de Root View
... se ve bien para el usuario, pero la herramienta Jerarquía de visualización de depuración revelará que la vista de Y aún está detrás de la vista de Z, dentro de una UITransitionView
. Es decir, después de los tres pasos anteriores, la jerarquía de vista es:
- UIWindow
- UITransitionView
- UIView (vista de Y)
- UIView (punto de vista de Z)
- UITransitionView
Sospecho que esto es un problema porque, en el momento de la transición, la vista de X no es realmente parte de la jerarquía de vista.
Si envío dismissViewControllerAnimated:NO
a X inmediatamente antes de transitionWithView:
la jerarquía de vistas resultante es:
- UIWindow
- UIView (vista de X)
- UIView (punto de vista de Z)
Si envío dismissViewControllerAnimated:
(SÍ o NO) a X, realice la transición en el bloque de completion:
luego la jerarquía de vista es correcta. Desafortunadamente, eso interfiere con la animación. Si se anima el despido, se pierde tiempo; si no está animando, parece roto.
Estoy intentando algunos otros enfoques (por ejemplo, crear una nueva clase de controlador de vista de contenedor para servir como mi controlador de vista raíz) pero no he encontrado nada que funcione. Actualizaré esta pregunta a medida que avance.
El objetivo final es pasar directamente de la vista presentada a un nuevo controlador de vista raíz y sin dejar jerarquías de vista perdida.
Enfrenté este problema y me molestó durante todo un día. Probé la solución obj-c de @ Rich y resulta que cuando quiero presentar otra viewController después, me bloquearán con un UITransitionView en blanco.
Finalmente, me di cuenta de esta manera y funcionó para mí.
- (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];
}
Bien, ahora todo lo que tienes que hacer es llamar a [self setRootViewController:newViewController];
cuando desee cambiar el controlador de vista raíz.
Intento algo simple que funciona para mí en iOs 9.3: simplemente elimine la vista de ViewController anterior de su jerarquía durante el dismissViewControllerAnimated
finalización de dismissViewControllerAnimated
.
Trabajemos en las vistas X, Y y Z explicadas por :
Es decir, esta secuencia de operaciones ...
- X se convierte en controlador de vista raíz
- X presenta Y, por lo que la vista de Y está en la pantalla
- Usando transitionWithView: para hacer que Z sea el nuevo controlador de Root View
Que dan:
////
//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)
En mi caso, X e Y están bien tratados y su punto de vista ya no está en jerarquía.
Llegué a este problema cuando usé este código:
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
})
}
Al deshabilitar este código, se solucionó el problema. Logré hacer que esto funcionara al habilitar solo esta animación de transición cuando se inicializa la barra de filtro que se anima.
En realidad, no es la respuesta que está buscando, pero podría llevarlo a la plataforma correcta para encontrar su solución.
Tuve un problema similar recientemente. Tuve que eliminar manualmente ese UITransitionView
de la ventana para solucionar el problema, luego llamar despedir en el controlador de vista raíz anterior para asegurar que se desasignó.
La solución no es muy buena, pero a menos que hayas encontrado una forma mejor desde la publicación de la pregunta, ¡es lo único que he encontrado que funciona! viewController
es solo el newController
de su pregunta original.
UIViewController *previousRootViewController = self.window.rootViewController;
self.window.rootViewController = viewController;
// Nasty hack to fix http://.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];
}];
Espero que esto también te ayude a solucionar tu problema, ¡es un dolor en el culo!
Swift 3.0
(Ver el historial de edición para otras versiones de Swift)
Para una implementación más agradable como una extensión en UIWindow
permite UIWindow
una transición opcional.
extension UIWindow {
/// Fix for http://.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()
}
}
}
}
Uso:
window.set(rootViewController: viewController)
O
let transition = CATransition()
transition.type = kCATransitionFade
window.set(rootViewController: viewController, withTransition: transition)