web-dev-qa-db-fra.com

Essayez de présenter UIViewController sur UIViewController dont la vue ne figure pas dans la hiérarchie des fenêtres

Je viens juste de commencer à utiliser Xcode 4.5 et j'ai eu cette erreur dans la console:

Avertissement: Essayez de présenter <finishViewController: 0x1e56e0a0> sur <ViewController: 0x1ec3e000> dont la vue ne figure pas dans la hiérarchie des fenêtres!

La vue est toujours présentée et tout dans l'application fonctionne bien. Est-ce quelque chose de nouveau dans iOS 6?

C'est le code que j'utilise pour changer de vue:

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

[self presentViewController:finished animated:NO completion:NULL];
525
Kyle Goslan

D'où appelez-vous cette méthode? J'ai eu un problème où je tentais de présenter un contrôleur de vue modale dans la méthode viewDidLoad. La solution pour moi était de déplacer cet appel vers la méthode viewDidAppear:.

Je suppose que la vuedu contrôleur de vue n'est pasdans la hiérarchie de la fenêtre au moment où elle a été chargée (lorsque le message viewDidLoad est envoyé), maisestdans la hiérarchie de la fenêtre après sa présentation (lorsque le message viewDidAppear: est envoyé).


Mise en garde

Si vous appelez presentViewController:animated:completion: dans viewDidAppear:, vous risquez de rencontrer un problème selon lequel le contrôleur de vue modal est toujours présenté chaque fois que la vue du contrôleur de vue apparaît (ce qui est logique!) Et que le contrôleur de vue modal présenté ne disparaîtra jamais. ...

Peut-être que ce n'est pas le meilleur endroit pour présenter le contrôleur de vue modale, ou peut-être qu'un état supplémentaire doit être conservé pour permettre au contrôleur de vue présentateur de décider s'il doit ou non présenter le contrôleur de vue modal immédiatement.

1136
James Bedford

Une autre cause potentielle:

J'ai eu ce problème quand je présentais accidentellement le même contrôleur de vue à deux reprises. (Une fois avec performSegueWithIdentifer:sender: qui a été appelé lorsque le bouton a été enfoncé et une seconde fois avec une séquence connectée directement au bouton).

Effectivement, deux divisions tiraient en même temps et j'ai eu l'erreur: Attempt to present X on Y whose view is not in the window hierarchy!

57
Chris Nolet

viewWillLayoutSubviews et viewDidLayoutSubviews (iOS 5.0+) peuvent être utilisés à cette fin. Ils sont appelés avant viewDidAppear.

34
Jonny

Pour afficher une sous-vue dans la vue principale, veuillez utiliser le code suivant

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

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

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

Pour supprimer toute sous-vue de la vue principale, veuillez utiliser le code suivant

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

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

[yourCurrentViewController dismissViewControllerAnimated:YES completion:nil];
21
abdul sathar

J'ai également rencontré ce problème lorsque j'ai essayé de présenter un contrôleur de vue dans viewDidLoad. J'ai essayé la réponse de James Bedford. Cela fonctionne, mais mon application affiche d'abord le fond pendant 1 ou 2 secondes. 

Après des recherches, j'ai enfin trouvé un autre moyen de résoudre ce problème: utiliser le contrôleur de vue enfant.

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

Probablement, comme moi, vous avez une mauvaise racine viewController

Je veux afficher un ViewController dans un contexte non-UIViewController,

Donc je ne peux pas utiliser un tel code:

[self presentViewController:]

Alors, je reçois un UIViewController:

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

Pour une raison quelconque (bug logique), rootViewController est autre chose que prévu (un UIViewController normal). Ensuite, je corrige le bogue en remplaçant rootViewController par un UINavigationController et le problème a disparu.

13
Sanbrother

TL; DR Vous ne pouvez avoir qu’un seul rootViewController et le dernier présenté. Donc, n'essayez pas de faire en sorte qu'un contrôleur de vue présente un autre contrôleur de vue s'il en a déjà présenté un qui n'a pas été rejeté.

Après avoir effectué certains de mes propres tests, je suis parvenu à une conclusion.

Si vous avez un rootViewController que vous voulez tout présenter, vous pouvez rencontrer ce problème.

Voici mon code rootController (open est mon raccourci pour présenter un viewcontroller à partir de la racine).

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 j'appelle ouvert deux fois de suite (quel que soit le temps écoulé), cela fonctionnera parfaitement à la première ouverture, mais PAS à la deuxième ouverture. La deuxième tentative d'ouverture entraînera l'erreur ci-dessus. 

Toutefois, si je ferme la dernière vue présentée puis appelle open, cela fonctionne parfaitement lorsque j'appelle à nouveau open (sur un autre contrôleur de vue).

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

Ce que j'ai conclu, c'est que le rootViewController de seulement l'appel le plus récent est dans la hiérarchie de la vue (même si vous ne l'avez pas ignoré ou supprimé d'une vue). J'ai essayé de jouer avec tous les appels du chargeur (viewDidLoad, viewDidAppear et de faire des appels d'envoi différé) et j'ai constaté que le seul moyen de le faire fonctionner était d'appeler UNIQUEMENT depuis le contrôleur de vue le plus haut. 

5
Aggressor

Mon problème était que j'intervenais dans la méthode UIApplicationDelegate de didFinishLaunchingWithOptions avant d'appeler makeKeyAndVisible() sur la fenêtre.

5
Adam Johns

Je me suis retrouvé avec un tel code qui fonctionne enfin pour moi (Swift), compte tenu du fait que vous souhaitez afficher des viewController de pratiquement n'importe où. Ce code va évidemment planter lorsqu'il n'y a pas de rootViewController disponible, c'est la fin ouverte. Il n’inclut pas non plus le passage généralement requis au fil d’interface utilisateur à l’aide de 

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

Si vous avez un objet AVPlayer avec une vidéo en cours de lecture, vous devez d'abord mettre la vidéo en pause.

3
Vlad

Ça fonctionne bien essayez ceci . Link

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

J'ai eu le même problème. Le problème était que le performSegueWithIdentifier était déclenché par une notification, dès que j'ai placé la notification sur le thread principal, le message d'avertissement avait disparu. 

3
Red

J'ai eu le même problème. Je devais intégrer un contrôleur de navigation et présenter le contrôleur à travers. Voici l'exemple de code.

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

}
2
resting

Au cas où cela aiderait quelqu'un, mon problème était extrêmement stupide. Totalement ma faute bien sûr. Une notification déclenchait une méthode qui appelait le modal. Mais je ne retirais pas la notification correctement, donc à un moment donné, j'aurais plus d'une notification, de sorte que le modal serait appelé plusieurs fois. Bien sûr, après avoir appelé une fois le modal, le contrôleur de vue qui l'appelle n'est plus dans la hiérarchie des vues, c'est pourquoi nous voyons ce problème. Ma situation a également causé bien d'autres problèmes, comme vous vous en doutez.

Donc, pour résumer, quoi que vous fassiez assurez-vous que le modal n’est pas appelé plus d’une fois .

2
HotFudgeSunday

Dans ma situation, je n'étais pas capable de mettre le mien dans une dérogation de classe. Alors, voici ce que j'ai eu:

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

Il m'est arrivé que le segue dans le storyboard soit une sorte de cassé. La suppression de la séquence (et la création de la même séquence à nouveau) a résolu le problème.

1
vonox7

Je dois écrire en dessous de la ligne.

self.searchController.definesPresentationContext = true

au lieu de 

self.definesPresentationContext = true

dans UIViewController 

1
Coder_A_D

Vous pouvez également obtenir cet avertissement lorsque vous effectuez une transition à partir d'un contrôleur de vue intégré à un conteneur. La solution correcte consiste à utiliser segue à partir du parent du conteneur, et non du contrôleur de vue du conteneur.

1
Borzh

Avec votre fenêtre principale, il y aura probablement toujours des moments avec des transitions incompatibles avec la présentation d'une alerte. Afin de permettre la présentation d'alertes à tout moment du cycle de vie de votre application, vous devez disposer d'une fenêtre distincte pour effectuer le travail.

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

Utilisation de base qui, par sa conception, aura toujours du succès:

[AlertWindow presentAlertWithTitle:@"My title" message:@"My message"];
1
Cœur

Avec Swift 3 ...

Une autre cause possible de ceci, ce qui m'est arrivé, a été d'avoir un passage d'une tableViewCell à un autre ViewController sur le Storyboard. J'ai également utilisé override func prepare(for segue: UIStoryboardSegue, sender: Any?) {} lorsque la cellule a été cliquée.

J'ai résolu ce problème en faisant une transition entre ViewController et ViewController.

1
Devbot10

J'ai eu ce problème, et la cause principale était de s'abonner à un gestionnaire de clic sur le bouton (TouchUpInside) plusieurs fois.

Il s'abonnait dans ViewWillAppear, appelé plusieurs fois depuis que nous avions ajouté la navigation pour accéder à un autre contrôleur, puis s'y détendre.

1
Wes

J'ai juste eu ce problème aussi, mais cela n'avait rien à voir avec le timing. J'utilisais un singleton pour gérer les scènes et je l'ai défini comme présentateur. En d'autres termes, "Soi" n'était lié à rien. Je viens de faire de sa "scène" intérieure le nouveau présentateur et le tour est joué, cela a fonctionné. (Voila perd son contact après que vous ayez appris sa signification, heh).

Donc oui, il ne s'agit pas de "trouver par magie le bon chemin", mais de comprendre où en est votre code et ce qu'il fait. Je suis heureux que Apple ait envoyé un message d'avertissement aussi clair en anglais, même avec émotion. Bravo au dev Apple qui a fait ça !!

0
Stephen J

Je l'ai corrigé en déplaçant la fonction start() à l'intérieur du bloc d'achèvement dismiss:

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

Démarrer contient deux appels à self.present(), un pour UINavigationController et un autre pour UIImagePickerController.

Cela a réglé le problème pour moi.

0
Alper

Si, pour une raison quelconque, les autres solutions ne vous conviennent pas, vous pouvez quand même utiliser ce bon vieux workaround de présenter avec le délai 0, comme ceci:

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

Bien que je n'ai vu aucune garantie documentée que votre VC soit dans la hiérarchie des vues lorsque le bloc de répartition est programmé pour être exécuté, j'ai constaté que cela fonctionnerait parfaitement. 

Utiliser un délai de, par exemple 0,2 seconde est également une option. Et la meilleure chose - de cette façon, vous n'avez pas besoin de jouer avec la variable booléenne dans viewDidAppear:

0
Dannie P

Malheureusement, la solution acceptée n'a pas fonctionné pour mon cas. J'essayais de naviguer vers un nouveau contrôleur de vue juste après le retrait d'un autre contrôleur de vue.

J'ai trouvé une solution en utilisant un drapeau pour indiquer quelle séquence de déroulement s'appelait.

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

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

Présentez ensuite le VC voulu avec 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
    }
}

Fondamentalement, le code ci-dessus me permet d'interrompre la procédure de connexion/SignUp VC et d'accéder au tableau de bord, ou d'interpréter l'action de décompression à partir du mot de passe oublié VC et de naviguer vers la page de connexion.

0
Fan Jin

Ce type d'avertissement peut signifier que vous essayez de présenter les nouveaux View Controller à Navigation Controller alors que ce Navigation Controller présente actuellement un autre View Controller. Pour résoudre ce problème, vous devez d'abord rejeter le View Controller actuel, puis le nouveau . Une autre cause de l'avertissement peut être la tentative de présenter View Controller sur un thread autre que main.

0
saltwat5r