Intente presentar UIViewController en UIViewController cuya vista no está en la jerarquía de ventanas

599

Acabo de comenzar a usar Xcode 4.5 y recibí este error en la consola:

Advertencia: ¡Intente presentar <finishViewController: 0x1e56e0a0> en <ViewController: 0x1ec3e000> cuya vista no está en la jerarquía de la ventana!

La vista aún se presenta y todo en la aplicación funciona bien. ¿Es esto algo nuevo en iOS 6?

Este es el código que estoy usando para cambiar entre vistas:

UIStoryboard *storyboard = self.storyboard;
finishViewController *finished = 
[storyboard instantiateViewControllerWithIdentifier:@"finishViewController"];

[self presentViewController:finished animated:NO completion:NULL];
Kyle Goslan
fuente
3
Tengo exactamente el mismo problema, excepto tratar de llamar presentViewController:animated:completiona un controlador de navegación. ¿Estás haciendo esto en el delegado de la aplicación?
tarnfeld
No, lo estoy haciendo de un controlador de vista a otro. ¿Has encontrado alguna solución?
Kyle Goslan
El mismo problema en una parte del código que siempre funcionó antes de usar Xcode 4.5, presento un UINavigationController, pero nuevamente esto siempre funcionó antes.
Emanuele Fumagalli
Tengo el mismo problema, no resuelto. Haciéndolo desde el delegado de la aplicación, y el rootviewcontroller llamando "presentViewController" siendo un UITabBarController.
darksider
3
también, si llama a este método antes de llamar a makeKeyAndVisible, muévalo después de eso
mike_haney

Respuestas:

1296

¿Desde dónde llamas a este método? Tuve un problema en el que intentaba presentar un controlador de vista modal dentro del viewDidLoadmétodo. La solución para mí fue trasladar esta llamada al viewDidAppear:método.

Mi suposición es que la vista del controlador de vista no está en la jerarquía de vista de la ventana en el punto en que se ha cargado (cuando viewDidLoadse envía el mensaje), pero está en la jerarquía de la ventana después de que se haya presentado (cuando viewDidAppear:se envía el mensaje) .


Precaución

Si usted hace una llamada a presentViewController:animated:completion:la viewDidAppear:que puede ejecutar en un problema por el que siempre se presenta el controlador de vista modal siempre que aparezca la opinión de la vista controlador (lo cual tiene sentido!) Y por lo que el controlador de vista modal que se presenta nunca desaparecerá .. .

Tal vez este no sea el mejor lugar para presentar el controlador de vista modal, o tal vez sea necesario mantener algún estado adicional que permita al controlador de vista de presentación decidir si debe o no presentar el controlador de vista modal de inmediato.

James Bedford
fuente
66
@ James está en lo cierto, la vista, aparentemente, no está en la jerarquía hasta después viewWillAppear se ha resuelto y una vez viewDidAppear ha sido llamado. Si esta fuera mi pregunta, aceptaría esta respuesta;)
Matt Mc
66
@james Gracias. El uso de ViewDidAppear resolvió el problema para mí también. Tiene sentido.
Ali
44
Desearía poder votar esto dos veces. Acabo de tener este problema y llegué al hilo para encontrar que ya había votado la última vez que vi esto.
Schrockwell
77
Tenga en cuenta que cuando cambia el VC en viewDidAppear, esto provoca la ejecución de un segue, con Animación. Provoca un destello / visualización del fondo.
Vincent
2
Sí, el truco es viewWillAppear: (BOOL) animado como es correcto. Una cosa más importante que debes llamar super en el método, como [super viewDidAppear: animated]; sin esto no funciona.
BootMaker
66

Otra posible causa:

Tuve este problema cuando accidentalmente presentaba el mismo controlador de vista dos veces. (Una vez con lo performSegueWithIdentifer:sender:que se llamó cuando se presionó el botón, y una segunda vez con un segmento conectado directamente al botón).

Efectivamente, dos segues se dispararon al mismo tiempo, y obtuve el error: Attempt to present X on Y whose view is not in the window hierarchy!

Chris Nolet
fuente
44
Tuve el mismo error, y su respuesta aquí me ayudó a descubrir qué estaba pasando, de hecho fue este error, lo arreglé por usted, señor, gracias, +1
samouray
1
Eliminé el viejo segmento y conecté VC a VC. ¿Hay alguna manera de conectar el botón al storyBoard al VC porque esa forma sigue siendo un error para mí?
MCB
Tuve el mismo error, su respuesta resolvió mi problema, gracias por su atención. Saludos cordiales.
iamburak
1
lol, accidentalmente también estaba creando dos vc: desde el botón y performSegue, ¡gracias por el consejo!
Borzh
1
En mi caso, estaba llamando present(viewController, animated: true, completion: nil)dentro de un bucle.
Samo
38

viewWillLayoutSubviewsy viewDidLayoutSubviews(iOS 5.0+) se pueden usar para este propósito. Se llaman antes que viewDidAppear.

Jonny
fuente
3
Todavía se usan también en otras ocasiones, así que creo que podrían llamarse varias veces en la "vida" de una vista.
Jonny
Tampoco es para qué sirven los métodos, como su nombre indica. viewDidAppear es correcto. Leer sobre el ciclo de vida de la vista es una buena idea.
tooluser
Esta es la mejor solución. En mi caso, presentar en viewDidAppear provoca una muestra de una fracción de segundo del controlador de vista antes de que se cargue el modal, lo cual es inaceptable.
TMilligan
Esta respuesta funcionó mejor para mí al intentar mostrar una alerta. La alerta no se mostraba cuando la ponía en viewDidLoad y viewWillAppear.
uplearnedu.com
26

Para mostrar cualquier subvista a la vista principal, utilice el siguiente código

UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

while (yourCurrentViewController.presentedViewController) 
{
   yourCurrentViewController = yourCurrentViewController.presentedViewController;
}

[yourCurrentViewController presentViewController:composeViewController animated:YES completion:nil];

Para descartar cualquier subvista de la vista principal, utilice el siguiente código

UIViewController *yourCurrentViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

while (yourCurrentViewController.presentedViewController) 
{
   yourCurrentViewController = yourCurrentViewController.presentedViewController;
}

[yourCurrentViewController dismissViewControllerAnimated:YES completion:nil];
abdul sathar
fuente
También funcionó para mí
Aziz Javed
17

También me encontré con este problema cuando intenté presentar un UIViewControllerin viewDidLoad. La respuesta de James Bedford funcionó, pero mi aplicación mostró el fondo primero durante 1 o 2 segundos.

Después de investigar un poco, he encontrado una manera de resolver esto usando el addChildViewController.

- (void)viewDidLoad
{
    ...
    [self.view addSubview: navigationViewController.view];
    [self addChildViewController: navigationViewController];
    ...
}
sunkehappy
fuente
99
Creo que te falta [navigationViewController didMoveToParentViewController: self]
foFox
2
Probé su código, junto con la sugerencia de foFox, y cuando voy a eliminarlo de su padre, no desaparecerá. Lololol Todavía atascado sin solución.
Logicsaurus Rex
Funciona en Swift3.1
Kang Byul
@sunkehappy por encima de dos líneas para usar antes de presentviewcontroller, pero se bloqueó ¿por qué?
Iyyappan Ravi
14

Probablemente, como yo, tienes una raíz incorrecta viewController

Quiero mostrar un ViewControlleren un non-UIViewControllercontexto,

Entonces no puedo usar ese código:

[self presentViewController:]

Entonces, obtengo un UIViewController:

[[[[UIApplication sharedApplication] delegate] window] rootViewController]

Por alguna razón (error lógico), el rootViewControlleres algo diferente de lo esperado (normal UIViewController). Luego corrijo el error, reemplazando rootViewControllercon a UINavigationController, y el problema se ha ido.

Sanbrother
fuente
8

TL; DR Solo puede tener 1 rootViewController y es el presentado más recientemente. Por lo tanto, no intente que un viewcontroller presente otro viewcontroller cuando ya se haya presentado uno que no se ha descartado.

Después de hacer algunas de mis propias pruebas, he llegado a una conclusión.

Si tiene un rootViewController que desea presentar todo, entonces puede encontrarse con este problema.

Aquí está mi código rootController (abrir es mi acceso directo para presentar un viewcontroller desde la raíz).

func open(controller:UIViewController)
{
    if (Context.ROOTWINDOW.rootViewController == nil)
    {
        Context.ROOTWINDOW.rootViewController = ROOT_VIEW_CONTROLLER
        Context.ROOTWINDOW.makeKeyAndVisible()
    }

    ROOT_VIEW_CONTROLLER.presentViewController(controller, animated: true, completion: {})
}

Si llamo a abrir dos veces seguidas (independientemente del tiempo transcurrido), esto funcionará bien en la primera apertura, pero NO en la segunda apertura. El segundo intento de apertura dará como resultado el error anterior.

Sin embargo, si cierro la vista presentada más recientemente y llamo a abrir, funciona bien cuando llamo a abrir nuevamente (en otro controlador de vista).

func close(controller:UIViewController)
{
    ROOT_VIEW_CONTROLLER.dismissViewControllerAnimated(true, completion: nil)
}

Lo que he concluido es que el rootViewController de solo MOST-RECENT-CALL está en la vista Jerarquía (incluso si no lo descartó o eliminó una vista). Traté de jugar con todas las llamadas del cargador (viewDidLoad, viewDidAppear y haciendo llamadas de despacho retrasado) y descubrí que la única forma en que puedo hacer que funcione es SOLO llamando al presente desde el controlador de la vista superior.

Agresor
fuente
Este parece ser un problema mucho más común que las muchas respuestas que han votado. Desafortunadamente, esto fue extremadamente útil
rayepps
sí, todo bien, pero cuál es la solución ... tengo un hilo de trabajo en segundo plano que va a un servidor y muestra guiones gráficos a la izquierda y al centro y toda la metodología es falsa ... es horrible ... lo que debería ser una brisa es absolutamente una broma porque todo lo que quiero hacer en el hilo de fondo es: wait wait decide push screen to fronty es IMPOSIBLE es IOS ????
Sr. Heelis
5

Mi problema fue que estaba realizando el segue en UIApplicationDelegate's didFinishLaunchingWithOptionsmétodo antes de llamar makeKeyAndVisible()a la ventana.

Adam Johns
fuente
¿cómo? ¿puedes elaborar? Estoy enfrentando el mismo problema. este es mi código de dejar initialViewControlleripad: UIViewController = mainStoryboardIpad.instantiateViewController (withIdentifier: "SplashController") como UIViewController self.window .rootViewController = initialViewControlleripad self.window .makeKeyAndVisible ()??
Shahtaj Khalid
4

En mi situación, no pude poner el mío en una anulación de clase. Entonces, esto es lo que obtuve:

let viewController = self // I had viewController passed in as a function,
                          // but otherwise you can do this


// Present the view controller
let currentViewController = UIApplication.shared.keyWindow?.rootViewController
currentViewController?.dismiss(animated: true, completion: nil)

if viewController.presentedViewController == nil {
    currentViewController?.present(alert, animated: true, completion: nil)
} else {
    viewController.present(alert, animated: true, completion: nil)
}
George_E
fuente
3

Yo tuve el mismo problema. Tuve que incrustar un controlador de navegación y presentar el controlador a través de él. A continuación se muestra el código de muestra.

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIImagePickerController *cameraView = [[UIImagePickerController alloc]init];
    [cameraView setSourceType:UIImagePickerControllerSourceTypeCamera];
    [cameraView setShowsCameraControls:NO];

    UIView *cameraOverlay = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 768, 1024)];
    UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"someImage"]];
    [imageView setFrame:CGRectMake(0, 0, 768, 1024)];
    [cameraOverlay addSubview:imageView];

    [cameraView setCameraOverlayView:imageView];

    [self.navigationController presentViewController:cameraView animated:NO completion:nil];
//    [self presentViewController:cameraView animated:NO completion:nil]; //this will cause view is not in the window hierarchy error

}
descansando
fuente
3

Si tiene un objeto AVPlayer con video reproducido, primero debe pausar el video.

Vlad
fuente
Quiero decir que el video debe detenerse / pausarse primero.
Vlad
en realidad esto me ayudó, me llevó a este stackoverflow.com/questions/20746413/…
Omar N Shamali
3

Tuve el mismo problema. El problema era que el performanceSegueWithIdentifier fue activado por una notificación, tan pronto como puse la notificación en el hilo principal, el mensaje de advertencia desapareció.

rojo
fuente
3

Está funcionando bien, prueba esto. Enlace

UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:secondView animated:YES completion: nil];
vijay
fuente
3

En caso de que ayude a alguien, mi problema fue extremadamente tonto. Totalmente mi culpa, por supuesto. Una notificación activaba un método que estaba llamando al modal. Pero no estaba eliminando la notificación correctamente, por lo que en algún momento, tendría más de una notificación, por lo que el modal se llamaría varias veces. Por supuesto, después de llamar al modal una vez, el controlador de vista que lo llama ya no está en la jerarquía de vistas, es por eso que vemos este problema. Mi situación también causó muchos otros problemas, como era de esperar.

Para resumir, haga lo que esté haciendo, asegúrese de que el modal no se llame más de una vez .

HotFudgeSunday
fuente
3

Terminé con un código que finalmente me funciona (Swift), teniendo en cuenta que desea mostrar algunos viewControllerdesde prácticamente cualquier lugar. Este código obviamente se bloqueará cuando no haya rootViewController disponible, ese es el final abierto. Tampoco incluye el cambio generalmente requerido al subproceso de interfaz de usuario usando

dispatch_sync(dispatch_get_main_queue(), {
    guard !NSBundle.mainBundle().bundlePath.hasSuffix(".appex") else {
       return; // skip operation when embedded to App Extension
    }

    if let delegate = UIApplication.sharedApplication().delegate {
        delegate.window!!.rootViewController?.presentViewController(viewController, animated: true, completion: { () -> Void in
            // optional completion code
        })
    }
}
igraczech
fuente
Por cierto, para entender DÓNDE llamo a este método desde ... es la biblioteca SDK sin UI, que muestra su propia IU sobre su aplicación en ciertos casos (no revelados).
igraczech
Se bloqueará y quemará si alguien decide incrustarle sdk en una aplicación que tiene una extensión. Pase un UIViewController para abusar de su método sdk init [s].
Anton Tropashko
Eres verdad Anton. Este código se escribió cuando Extensions no existía y el SDK aún no se usa en ninguno de ellos. He agregado una cláusula de guardia para omitir este caso límite.
igraczech
3

Este tipo de advertencia puede significar que usted está tratando de nuevo presente View Controllera través Navigation Controllermientras que esto Navigation Controllerestá presentando otra View Controller. Para solucionarlo, debe descartar el actual presentado View Controlleral principio y al finalizar presentar el nuevo. Otra causa de la advertencia puede ser intentar presentar View Controlleren un hilo distinto de main.

saltwat5r
fuente
3

Tuve un problema similar en Swift 4.2 pero mi vista no se presentó desde el ciclo de vista. Descubrí que tenía múltiples segue para ser presentado al mismo tiempo. Entonces usé dispatchAsyncAfter.

func updateView() {

 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in

// for programmatically presenting view controller 
// present(viewController, animated: true, completion: nil)

//For Story board segue. you will also have to setup prepare segue for this to work. 
 self?.performSegue(withIdentifier: "Identifier", sender: nil)
  }
}
Ashim Dahal
fuente
2

Lo arreglé moviendo la start()función dentro del dismissbloque de finalización:

self.tabBarController.dismiss(animated: false) {
  self.start()
}

Inicio contiene dos llamadas a self.present()una para un UINavigationController y otra para a UIImagePickerController.

Eso me lo arregló.

Alper
fuente
1

También puede obtener esta advertencia cuando realice una segue desde un controlador de vista que está incrustado en un contenedor. La solución correcta es usar segue del padre del contenedor, no del controlador de vista del contenedor.

Borzh
fuente
1

Tiene que escribir debajo de la línea.

self.searchController.definesPresentationContext = true

en vez de

self.definesPresentationContext = true

en UIViewController

Codificador_A_D
fuente
1

Con Swift 3 ...

Otra posible causa de esto, que me sucedió, fue pasar de una tabla ViewCell a otra ViewController en el Storyboard. También lo usé override func prepare(for segue: UIStoryboardSegue, sender: Any?) {}cuando se hizo clic en la celda.

Solucioné este problema al pasar de ViewController a ViewController.

Devbot10
fuente
1

Tuve este problema, y ​​la causa raíz fue suscribirse a un controlador de clic de botón (TouchUpInside) varias veces.

Se estaba suscribiendo en ViewWillAppear, que se llamaba varias veces desde que agregamos la navegación para ir a otro controlador y luego volver a ella.

Wes
fuente
1

Me sucedió que la segue en el guión gráfico estaba rota . Eliminar el segue (y crear exactamente el mismo segue nuevamente) resolvió el problema.

vonox7
fuente
1

Con su ventana principal, es probable que siempre haya momentos con transiciones que sean incompatibles con la presentación de una alerta. Para permitir la presentación de alertas en cualquier momento en el ciclo de vida de su aplicación, debe tener una ventana separada para hacer el trabajo.

/// independant window for alerts
@interface AlertWindow: UIWindow

+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message;

@end

@implementation AlertWindow

+ (AlertWindow *)sharedInstance
{
    static AlertWindow *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[AlertWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
    });
    return sharedInstance;
}

+ (void)presentAlertWithTitle:(NSString *)title message:(NSString *)message
{
    // Using a separate window to solve "Warning: Attempt to present <UIAlertController> on <UIViewController> whose view is not in the window hierarchy!"
    UIWindow *shared = AlertWindow.sharedInstance;
    shared.userInteractionEnabled = YES;
    UIViewController *root = shared.rootViewController;
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    alert.modalInPopover = true;
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        shared.userInteractionEnabled = NO;
        [root dismissViewControllerAnimated:YES completion:nil];
    }]];
    [root presentViewController:alert animated:YES completion:nil];
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];

    self.userInteractionEnabled = NO;
    self.windowLevel = CGFLOAT_MAX;
    self.backgroundColor = UIColor.clearColor;
    self.hidden = NO;
    self.rootViewController = UIViewController.new;

    [NSNotificationCenter.defaultCenter addObserver:self
                                           selector:@selector(bringWindowToTop:)
                                               name:UIWindowDidBecomeVisibleNotification
                                             object:nil];

    return self;
}

/// Bring AlertWindow to top when another window is being shown.
- (void)bringWindowToTop:(NSNotification *)notification {
    if (![notification.object isKindOfClass:[AlertWindow class]]) {
        self.hidden = YES;
        self.hidden = NO;
    }
}

@end

Uso básico que, por diseño, siempre tendrá éxito:

[AlertWindow presentAlertWithTitle:@"My title" message:@"My message"];
Coeur
fuente
1

Lamentablemente, la solución aceptada no funcionó para mi caso. Estaba tratando de navegar a un nuevo controlador de vista justo después de desconectarme de otro controlador de vista.

Encontré una solución usando una bandera para indicar qué desenvolvimiento se llamó.

@IBAction func unwindFromAuthenticationWithSegue(segue: UIStoryboardSegue) {
    self.shouldSegueToMainTabBar = true
}

@IBAction func unwindFromForgetPasswordWithSegue(segue: UIStoryboardSegue) {
    self.shouldSegueToLogin = true
}

Luego presente el VC deseado con present(_ viewControllerToPresent: UIViewController)

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    if self.shouldSegueToMainTabBar {
        let mainTabBarController = storyboard.instantiateViewController(withIdentifier: "mainTabBarVC") as! MainTabBarController
        self.present(mainTabBarController, animated: true)
        self.shouldSegueToMainTabBar = false
    }
    if self.shouldSegueToLogin {
        let loginController = storyboard.instantiateViewController(withIdentifier: "loginVC") as! LogInViewController
        self.present(loginController, animated: true)
        self.shouldSegueToLogin = false
    }
}

Básicamente, el código anterior me permitirá capturar la desconexión del inicio de sesión / Registrarse VC y navegar al panel de control, o capturar la acción de desconexión de olvidar la contraseña VC y navegar a la página de inicio de sesión.

Fan Jin
fuente
1

Solucioné este error al almacenar la mayoría del viewcontroller superior en constante que se encuentra dentro del ciclo sobre rootViewController:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }
    topController.present(controller, animated: false, completion: nil)
    // topController should now be your topmost view controller
}
Marek Baláž
fuente
1

Encontré que este error llegó después de actualizar Xcode, creo que Swift 5 . El problema estaba ocurriendo cuando programáticamente inicié un segue directamente después de desenrollar un controlador de vista.

La solución llegó mientras se solucionaba un error relacionado, que era que el usuario ahora podía desenvolverse segues deslizando la página hacia abajo. Esto rompió la lógica de mi programa.

Se solucionó cambiando el modo Presentación en todos los controladores de vista de Automático a Pantalla completa .

Puede hacerlo en el panel de atributos en el generador de interfaces. O vea esta respuesta para saber cómo hacerlo programáticamente.

Tim MB
fuente
0

También tuve este problema, pero no tenía nada que ver con el momento. Estaba usando un singleton para manejar escenas, y lo configuré como presentador. En otras palabras, "Self" no estaba conectado a nada. Acabo de hacer su "escena" interior el nuevo presentador y listo, funcionó. (Voila pierde su toque después de que aprendes su significado, je).

Entonces, sí, no se trata de "encontrar mágicamente el camino correcto", se trata de entender dónde se encuentra su código y qué está haciendo. Estoy feliz de que Apple haya dado un mensaje de advertencia tan simple en inglés, incluso con emoción. ¡Felicitaciones al desarrollador de Apple que hizo eso!

Stephen J
fuente
0

Si otras soluciones no se ven bien por alguna razón, aún puede usar este viejo workaroundanticipo de presentar con el retraso de 0, así:

dispatch_after(0, dispatch_get_main_queue(), ^{
    finishViewController *finished = [self.storyboard instantiateViewControllerWithIdentifier:@"finishViewController"];
    [self presentViewController:finished animated:NO completion:NULL];    
});

Si bien no he visto ninguna garantía documentada de que su VC estaría en la jerarquía de vistas en el momento en que el bloque de despacho está programado para ejecutarse, he observado que funcionaría bien.

Usar una demora de, por ejemplo, 0.2 segundos también es una opción. Y lo mejor: de esta manera no necesita meterse con la variable booleana enviewDidAppear:

Dannie P
fuente
2
Si bien esto podría funcionar ahora, no hay garantía de que Apple cambie el comportamiento y lo deje en el futuro. Luego, cuando vuelva a mirar su código para intentar arreglar las cosas nuevamente, se preguntará por qué estaba haciendo este envío aparentemente innecesario.
Cruinh
El envío con 0 tiempo ya me ha ahorrado varias veces, a veces las cosas que lógicamente deberían funcionar normalmente no funcionan sin él. Así que solo haga comentarios para usted y para los demás sobre por qué hace cosas no obvias (no solo ese envío) y debería estar bien.
Dannie P
2
Simplemente estás pirateando el problema y no lo estás resolviendo.
Michael Peterson
0

Esto funciona para presentar cualquier controlador de vista, si tiene un controlador de navegación disponible. self.navigationController? .present (MyViewController, animado: verdadero, finalización: nulo) Además, puedo presentar alertas y el controlador de correo también.

Gurpreet Singh
fuente