Tentativa de apresentar UIViewController no UIViewController cuja exibição não está na hierarquia da janela


599

Comecei a usar o Xcode 4.5 e recebi esse erro no console:

Aviso: Tente apresentar <finishViewController: 0x1e56e0a0> em <ViewController: 0x1ec3e000> cuja exibição não está na hierarquia da janela!

A visualização ainda está sendo apresentada e tudo no aplicativo está funcionando bem. Isso é algo novo no iOS 6?

Este é o código que estou usando para alternar entre visualizações:

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

[self presentViewController:finished animated:NO completion:NULL];

3
Estou tendo exatamente o mesmo problema, exceto tentando chamar presentViewController:animated:completionum controlador nav. Você está fazendo isso no delegado do aplicativo?
tarnfeld

Não, eu estou fazendo isso de um controlador de exibição para outro. Você encontrou alguma solução?
Kyle Goslan

Mesmo problema em uma parte do código que sempre funcionava antes do Xcode 4.5, estou apresentando um UINavigationController, mas novamente isso sempre funcionou antes.
Emanuele Fumagalli

Eu tenho o mesmo problema, não resolvido. Isso é feito pelo delegado do aplicativo e pelo controlador rootview que chama "presentViewController" sendo um UITabBarController.
darksider

3
Além disso, se chamar esse método antes de chamar makeKeyAndVisible, movê-lo depois disso
mike_haney

Respostas:


1296

De onde você está chamando esse método? Eu tive um problema em que estava tentando apresentar um controlador de exibição modal dentro do viewDidLoadmétodo. A solução para mim foi passar esta chamada para oviewDidAppear: método

Minha presunção é que a visualização do controlador de exibição não esteja na hierarquia da janela no ponto em que foi carregada (quando a viewDidLoadmensagem é enviada), mas está na hierarquia da janela após a apresentação (quando a viewDidAppear:mensagem é enviada) .


Cuidado

Se você fizer uma chamada presentViewController:animated:completion:noviewDidAppear: você pode executar em um problema no qual o controlador de vista modal está sempre sendo apresentados sempre View aparece do controlador de vista (o que faz sentido!) E assim o controlador de vista modal sendo apresentada nunca vai embora .. .

Talvez este não seja o melhor local para apresentar o controlador de exibição modal ou talvez seja necessário manter algum estado adicional que permita ao controlador de exibição decidir se deve ou não apresentar o controlador de exibição modal imediatamente.


6
@ James, você está correto, a exibição aparentemente não está na hierarquia até depois que o viewWillAppear for resolvido e depois que o viewDidAppear for chamado. Se esta fosse a minha pergunta eu aceitaria essa resposta;)
Matt Mc

6
@james Obrigado. Usar o ViewDidAppear também resolveu o problema para mim. Faz sentido.
Ali

44
Eu gostaria de poder votar isso duas vezes. Eu apenas tive esse problema e vim à discussão para descobrir que já havia votado na última vez que vi isso.
Schrockwell

7
Observe que quando você altera o VC no viewDidAppear, isso causa a execução de um segue, com Animação. Causa um flash / exibição do plano de fundo.
Vincent

2
Sim, o truque é o viewWillAppear: (BOOL) animado, pois está correto. Mais uma coisa importante que você precisa chamar de super no método, como [super viewDidAppear: animated]; sem isso, não está funcionando.
BootMaker # 30/13

66

Outra causa potencial:

Eu tive esse problema ao apresentar acidentalmente o mesmo controlador de exibição duas vezes. (Uma vez com a performSegueWithIdentifer:sender:qual foi chamado quando o botão foi pressionado e uma segunda vez com um segue conectado diretamente ao botão).

Efetivamente, dois segues foram disparados ao mesmo tempo e recebi o erro: Attempt to present X on Y whose view is not in the window hierarchy!


4
Eu tive o mesmo erro, e aqui a sua resposta me ajudou a descobrir o que estava acontecendo, ele era de fato esse erro, fixa-lo por causa de você, senhor, obrigado, +1
samouray

1
Apaguei o antigo segue e liguei o VC ao VC. Existe uma maneira de conectar o botão ao storyBoard no VC porque esse caminho continua me causando erros?
MCB 16/07

Eu tive o mesmo erro, sua resposta resolveu meu problema, obrigado por sua atenção. Atenciosamente.
Iamburak

1
rsrs, acidentalmente eu também estava criando dois vc's: from button e performSegue, obrigado pela dica !!!
315

1
No meu caso, eu estava ligando present(viewController, animated: true, completion: nil)dentro de um loop.
Samo

38

viewWillLayoutSubviewse viewDidLayoutSubviews(iOS 5.0 ou superior) pode ser usado para esse fim. Eles são chamados mais cedo que viewDidAppear.


3
Ainda eles são usados ​​também em outras ocasiões, então acho que podem ser chamados várias vezes na "vida útil" de uma exibição.
quer

Também não é para isso que servem os métodos - como o nome sugere. viewDidAppear está correto. Ler sobre o ciclo de vida da exibição é uma boa ideia.
tooluser

essa é a melhor solução. No meu caso, a apresentação em viewDidAppear causa uma exibição de fração de segundo do controlador de exibição antes do carregamento do modal, o que é inaceitável.
precisa saber é o seguinte

Essa resposta funcionou melhor para mim ao tentar exibir um alerta. O alerta não seria exibido quando eu o colocasse em viewDidLoad e viewWillAppear.
Uplearnedu.com

26

Para Exibir qualquer subvisão para a visualização principal, use o seguinte código

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

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

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

Para ignorar qualquer subview da vista principal, use o seguinte código

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

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

[yourCurrentViewController dismissViewControllerAnimated:YES completion:nil];

Trabalhou para mim também
Aziz Javed

17

Eu também encontrei este problema quando tentei apresentar um UIViewControllerno viewDidLoad. A resposta de James Bedford funcionou, mas meu aplicativo mostrou os antecedentes primeiro por 1 ou 2 segundos.

Após algumas pesquisas, encontrei uma maneira de resolver isso usando o addChildViewController.

- (void)viewDidLoad
{
    ...
    [self.view addSubview: navigationViewController.view];
    [self addChildViewController: navigationViewController];
    ...
}

9
Eu acho que está faltando [navigationViewController didMoveToParentViewController: self]
foFox

2
Eu tentei o seu código, junto com a sugestão do foFox, e quando for removê-lo do pai, ele não desaparecerá. Lololol. Ainda preso sem correção.
Logicsaurus Rex

Trabalha em Swift3.1
Kang Byul

@sunkehappy acima de duas linhas a serem usadas antes do presentviewcontroller, mas está travado por quê?
Iyyappan Ravi

14

Provavelmente, como eu, você tem uma raiz errada viewController

Eu quero exibir um ViewControllerem um non-UIViewControllercontexto,

Portanto, não posso usar esse código:

[self presentViewController:]

Então, eu recebo um UIViewController:

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

Por alguma razão (bug lógico), rootViewControlleré algo diferente do esperado (normal UIViewController). Corrijo o bug, substituindo-o rootViewControllerpor um UINavigationController, e o problema se foi.


8

TL; DR Você pode ter apenas 1 rootViewController e é o mais recente apresentado. Portanto, não tente que um viewcontroller apresente outro viewcontroller quando ele já tiver apresentado um que não tenha sido descartado.

Depois de fazer alguns dos meus próprios testes, cheguei a uma conclusão.

Se você tiver um rootViewController que deseja apresentar tudo, poderá encontrar esse problema.

Aqui está o meu código rootController (aberto é o meu atalho para apresentar um viewcontroller a partir da raiz).

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: {})
}

Se eu abrir aberto duas vezes seguidas (independentemente do tempo decorrido), isso funcionará bem na primeira abertura, mas NÃO na segunda abertura. A segunda tentativa aberta resultará no erro acima.

No entanto, se eu fechar a visualização apresentada mais recentemente e depois abrir, ela funcionará bem quando eu abrir novamente (em outro controlador de exibição).

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

O que concluí é que o rootViewController apenas da MOST-RECENT-CALL está na Hierarquia da exibição (mesmo que você não a tenha descartado ou removido uma exibição). Tentei jogar com todas as chamadas do carregador (viewDidLoad, viewDidAppear e fazer chamadas de despacho atrasadas) e descobri que a única maneira de fazê-lo funcionar é SOMENTE chamando presente do controlador de exibição mais superior.


Parece um problema muito mais comum que as muitas respostas que votaram nas suas. Infelizmente, isso foi extremamente útil
rayepps

sim tudo bem e bom, mas qual é a solução ... eu tenho um thread de trabalho em segundo plano indo para um servidor e exibindo storyboards à direita e no centro e toda a metodologia é falsa .. é horrível .. o que deveria ser uma brisa é absolutamente um piada, porque tudo o que eu quero fazer é que o thread de segundo plano seja: wait wait decide push screen to fronte é IMPOSSÍVEL o IOS ????
Sr. Heelis 9/05/19

5

Meu problema foi que eu estava realizando o segue em UIApplicationDelegate's didFinishLaunchingWithOptionsmétodo antes eu liguei makeKeyAndVisible()na janela.


como? Você pode elaborar? Estou enfrentando o mesmo problema. Este é o meu código para inicializarControlleripad: UIViewController = mainStoryboardIpad.instantiateViewController (withIdentifier: "SplashController") como UIViewController self.window? .rootViewController = initialViewControlleripad self.window? .makeKeyAndVisible (11)
11

4

Na minha situação, não pude colocar o meu em uma substituição de classe. Então, aqui está o que eu tenho:

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)
}

3

Eu tive o mesmo problema. Eu tive que incorporar um controlador de navegação e apresentá-lo através dele. Abaixo está o código de exemplo.

- (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

}


3

Eu tive o mesmo problema. O problema era que o performSegueWithIdentifier foi acionado por uma notificação, assim que eu coloquei a notificação no thread principal, a mensagem de aviso desapareceu.


3

Está funcionando bem, tente isso. Ligação

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

3

Caso isso ajude alguém, meu problema foi extremamente tolo. Totalmente minha culpa, é claro. Uma notificação estava acionando um método que estava chamando o modal. Mas eu não estava removendo a notificação corretamente, então, em algum momento, eu teria mais de uma notificação, para que o modal fosse chamado várias vezes. Obviamente, depois que você chama o modal uma vez, o controlador de exibição que o chama não está mais na hierarquia de visualizações, é por isso que vemos esse problema. Minha situação também causou muitos outros problemas, como seria de esperar.

Então, para resumir, o que você estiver fazendo, verifique se o modal não está sendo chamado mais de uma vez .


3

Acabei com um código que finalmente funciona para mim (Swift), considerando que você deseja exibir alguns viewControllerde praticamente qualquer lugar. Esse código obviamente travará quando não houver rootViewController disponível, esse é o final aberto. Também não inclui a mudança geralmente necessária para o thread da interface do usuário 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
        })
    }
}

BTW para entender de onde eu chamo esse método de ... é a biblioteca SDK sem interface do usuário, que exibe sua própria interface do usuário sobre seu aplicativo em certos casos (não revelados).
igraczech

Você falhará e queimará se alguém decidir incorporar você sdk em um aplicativo que tenha uma extensão. Passe um UIViewController para abusar dos métodos sdk init.
Anton Tropashko

Você é verdadeiro Anton. Este código foi escrito quando as extensões não existiam e o SDK ainda não foi usado em nenhuma delas. Eu adicionei uma cláusula de guarda para pular esse caso extremo.
amigos estão dizendo sobre igraczech

3

Este tipo de aviso pode significar que você está tentando apresentar novas View Controlleratravés Navigation Controllerenquanto este Navigation Controllerestá atualmente apresentando outra View Controller. Para corrigi-lo Você deve descartar o que é apresentado atualmente View Controllerno início e, após a conclusão, apresentar o novo. Outra causa do aviso pode estar tentando apresentar View Controllerno thread outro que não main.


3

Eu tive um problema semelhante no Swift 4.2, mas minha visão não foi apresentada no ciclo de visualização. Eu descobri que tinha vários seguimentos a serem apresentados ao mesmo tempo. Então eu usei 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)
  }
}

2

Corrigi-o movendo a start()função dentro do dismissbloco de conclusão:

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

O Start contém duas chamadas para self.present()uma para um UINavigationController e outra para umaUIImagePickerController .

Isso consertou para mim.


1

Você também pode receber esse aviso ao executar uma sequência de um controlador de exibição incorporado em um contêiner. A solução correta é usar segue do pai do contêiner, não do controlador de exibição do contêiner.


1

Tem que escrever abaixo da linha.

self.searchController.definesPresentationContext = true

ao invés de

self.definesPresentationContext = true

em UIViewController


1

Com Swift 3 ...

Outra causa possível para isso, que aconteceu comigo, foi passar de um tableViewCell para outro ViewController no Storyboard. Eu também usei override func prepare(for segue: UIStoryboardSegue, sender: Any?) {}quando a célula foi clicada.

Corrigi esse problema fazendo um segue do ViewController para o ViewController.


1

Eu tive esse problema e a causa principal foi assinar um manipulador de cliques de botão (TouchUpInside) várias vezes.

Ele estava assinando o ViewWillAppear, que estava sendo chamado várias vezes desde que adicionamos navegação para ir para outro controlador e, em seguida, voltamos a ele.


1

Aconteceu-me que as instruções no storyboard estavam meio que quebradas . A exclusão do segue (e a criação exata do mesmo segue novamente) resolveu o problema.


1

Com sua janela principal, sempre haverá momentos com transições incompatíveis com a apresentação de um alerta. Para permitir a apresentação de alertas a qualquer momento no ciclo de vida do aplicativo, você deve ter uma janela separada para fazer o trabalho.

/// 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 design, sempre terá êxito:

[AlertWindow presentAlertWithTitle:@"My title" message:@"My message"];

1

Infelizmente, a solução aceita não funcionou no meu caso. Eu estava tentando navegar para um novo View Controller logo após relaxar de outro View Controller.

Encontrei uma solução usando um sinalizador para indicar qual segue de desenrolar foi chamado.

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

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

Em seguida, apresente o VC desejado com 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
    }
}

Basicamente, o código acima me permite capturar o desenrolar do login / SignUp VC e navegar até o painel, ou capturar a ação desenrolar do esquecer a senha VC e navegar para a página de login.


1

Corrigi este erro com o armazenamento da maioria dos controladores de visualização em constante, que é encontrada durante o ciclo sobre o 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
}

1

Descobri que esse bug chegou após a atualização do Xcode, acredito no Swift 5 . O problema estava acontecendo quando eu iniciei um segue programaticamente diretamente após desenrolar um controlador de exibição.

A solução chegou ao consertar um bug relacionado, que é que o usuário agora conseguia descontrair os seguidores deslizando a página para baixo. Isso quebrou a lógica do meu programa.

Foi corrigido alterando o modo Apresentação em todos os controladores de exibição de Automático para Tela inteira .

Você pode fazer isso no painel de atributos no construtor de interfaces. Ou veja esta resposta para saber como fazê-lo programaticamente.


0

Eu também tinha esse problema, mas não tinha nada a ver com o tempo. Eu estava usando um singleton para lidar com cenas, e o defini como apresentador. Em outras palavras, o "eu" não estava ligado a nada. Acabei de fazer da sua "cena" interior o novo apresentador e pronto, funcionou. (Voila perde o toque depois que você aprende o significado, heh).

Então, sim, não se trata de "encontrar magicamente o caminho certo", é entender como o seu código está e o que está fazendo. Estou feliz que a Apple tenha transmitido uma mensagem de aviso em inglês tão claro, mesmo com emoção. Parabéns ao desenvolvedor da apple que fez isso !!


0

Se outras soluções não parecerem boas por algum motivo, você ainda pode usar esse bom e velho workaroundapresentar com o atraso 0, assim:

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

Embora eu não tenha visto nenhuma garantia documentada de que seu VC estaria na hierarquia de exibição no horário em que o bloco de despacho está agendado para execução, observei que ele funcionaria perfeitamente.

Usar atraso de, por exemplo, 0,2 s também é uma opção. E a melhor coisa - dessa forma, você não precisa mexer com a variável booleanaviewDidAppear:


2
Embora isso possa funcionar agora, não há garantia de que a Apple altere o comportamento e o interrompa no futuro. Então, quando você voltar a olhar seu código para tentar consertar as coisas novamente, você se perguntará por que estava fazendo esse despacho aparentemente desnecessário.
Cruinh

A expedição com tempo 0 já me salvou várias vezes - às vezes coisas que deveriam funcionar logicamente normalmente não funcionam sem ela. Portanto, basta fazer comentários para si e para outras pessoas sobre por que você faz coisas não óbvias (não apenas esse tipo de despacho) e você deve ficar bem.
Dannie P

2
Você está apenas tentando solucionar o problema e não está resolvendo.
22616 Michael Peterson

0

Isso funciona para apresentar qualquer controlador de exibição, se você tiver um controlador de navegação disponível. self.navigationController? .present (MyViewController, animado: true, conclusão: nil) Além disso, também posso apresentar alertas e controlador de correio.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.