Попытка представить UIViewController на UIViewController, представление которого не находится в иерархии окон


только начал использовать Xcode 4.5 и я получил эту ошибку в консоли:

внимание: попытка представить на чье представление не находится в иерархии окон!

представление все еще представлено, и все в приложении работает нормально. Это что-то новое в iOS 6?

Это код, который я использую для переключения между видом:

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

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

26 ответов:

откуда вы вызываете этот метод? У меня была проблема, когда я пытался представить модальный контроллер вида в viewDidLoad метод. Решение для меня было переместить этот вызов в viewDidAppear: метод.

моя презумпция заключается в том, что вид контроллера не в иерархии представления окна в точке его загрузки (когда viewDidLoad сообщение отправлено), но это и в окне иерархия после того, как она была представлена (когда сообщение).


осторожностью

если вы сделаете звонок presentViewController:animated:completion: на viewDidAppear: вы можете столкнуться с проблемой, при которой контроллер модального вида всегда отображается всякий раз, когда появляется представление контроллера вида (что имеет смысл!) и поэтому представленный контроллер модального вида никогда не исчезнет...

может быть, это не лучшее место, чтобы представить модальный вид контроллер или, возможно, необходимо сохранить некоторое дополнительное состояние, которое позволяет контроллеру представления представления решить, следует ли ему немедленно представить контроллер модального представления.

еще одна потенциальная причина:

у меня была эта проблема, когда я случайно представлял один и тот же контроллер Вида дважды. (Один раз с performSegueWithIdentifer:sender: который был вызван, когда кнопка была нажата, и во второй раз с переходом подключен напрямую к кнопке).

фактически, два сегмента стреляли одновременно, и я получил ошибку:Attempt to present X on Y whose view is not in the window hierarchy!

viewWillLayoutSubviews и viewDidLayoutSubviews (iOS 5.0+) может быть использован для этой цели. Они вызываются раньше, чем viewDidAppear.

для отображения любого подвида в основной вид, пожалуйста, используйте следующий код

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

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

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

для отклонения любого подвида из основного вида, пожалуйста, используйте следующий код

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

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

[yourCurrentViewController dismissViewControllerAnimated:YES completion:nil];

Я тоже столкнулся с этой проблемой, когда я пытался представить контроллер представления в viewDidLoad. Я попробовал ответить Джеймсу Бедфорду. Это работает, но мое приложение будет показывать фон сначала в течение 1 или 2 секунд.

после исследования я, наконец, найти другой способ решить это является использование дочернего контроллера вида.

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

наверное, как и у меня, у вас неправильный корень viewController

Я хочу, чтобы отобразить ViewController на non-UIViewController связи

поэтому я не могу использовать такой код:

[self presentViewController:]

Итак, я получаю UIViewController:

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

по какой-то причине (логическая ошибка),rootViewController Это что-то другое, чем ожидалось (нормальный UIViewController). Затем я исправляю ошибку, заменяя rootViewController С UINavigationController, и проблема ушла.

TL; DR у вас может быть только 1 rootViewController и его последний представленный. Поэтому не пытайтесь заставить viewcontroller представить другой viewcontroller, когда он уже представлен, который не был отклонен.

после выполнения некоторых из моих собственных испытаний я пришел к выводу.

Если у вас есть rootViewController, который вы хотите представить все, то вы можете столкнуться с этой проблемой.

вот мой код rootController (open is мой ярлык для представления viewcontroller из корня).

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

Если я вызываю open дважды подряд (независимо от времени, прошедшего), это будет работать просто отлично на первом открытом, но не на втором открытом. Вторая открытая попытка приведет к ошибке выше.

однако если я закрою самый последний представленный вид, то вызовите open, он отлично работает, когда я снова вызываю open (на другом viewcontroller).

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

Я пришел к выводу, что rootViewController только самого последнего вызова находится в иерархии представлений (даже если вы не уволили его или не удалили представление). Я пробовал играть со всеми вызовами загрузчика (viewDidLoad, viewDidAppear и выполнение отложенных вызовов диспетчеризации), и я обнаружил, что единственный способ заставить его работать-это вызывать только с самого верхнего контроллера вида.

моя проблема заключалась в том, что я выполнял сегмент в UIApplicationDelegate ' s didFinishLaunchingWithOptions метод прежде, чем я позвонила makeKeyAndVisible() на окне.

Я закончил с таким кодом, который, наконец, работает для меня (Swift), учитывая, что вы хотите отобразить некоторые viewController практически из любого места. Этот код, очевидно, сбой, когда нет rootViewController доступны, это открытый конец. Он также не включает обычно необходимый переключатель в поток пользовательского интерфейса с помощью

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

Если у вас есть объект AVPlayer с воспроизведенным видео, вы должны сначала приостановить видео.

У меня была та же проблема. Проблема заключалась в том, что performSegueWithIdentifier был вызван уведомлением, как только я поместил уведомление в основной поток, предупреждающее сообщение исчезло.

это работает нормально, попробуйте это.ссылке

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

в случае, если это кому-то поможет, мой вопрос был очень глупым. Конечно, это полностью моя вина. Уведомление запускало метод, который вызывал модальный. Но я не удалял уведомление правильно, поэтому в какой-то момент у меня будет более одного уведомления, поэтому модальный вызов будет вызван несколько раз. Конечно, после вызова модального один раз, viewcontroller, который его вызывает, больше не находится в иерархии представлений, поэтому мы видим эту проблему. Моя ситуация вызвала кучу других вопрос тоже, как и следовало ожидать.

Итак, подводя итог, что бы вы ни делали убедитесь, что модальный не вызывается более одного раза.

У меня была та же проблема. Мне пришлось встроить навигационный контроллер и представить контроллер через него. Ниже приведен пример кода.

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

}

вы также можете получить это предупреждение при выполнении segue от контроллера вида, встроенного в контейнер. Правильное решение-использовать segue от родителя контейнера, а не от контроллера представления контейнера.

должны написать ниже строки.

self.searchController.definesPresentationContext = true

вместо

self.definesPresentationContext = true

в UIViewController

С Swift 3...

еще одна возможная причина этого, которая произошла со мной, состояла в том, чтобы перейти от tableViewCell к другому ViewController на раскадровке. Я также использовал override func prepare(for segue: UIStoryboardSegue, sender: Any?) {} когда ячейка была нажата.

я исправил эту проблему, сделав переход от ViewController к ViewController.

У меня была эта проблема, и основной причиной была подписка на обработчик нажатия кнопки (TouchUpInside) несколько раз.

он подписывался на ViewWillAppear, который вызывался несколько раз, так как мы добавили навигацию, чтобы перейти к другому контроллеру, а затем вернуться к нему.

Если другие решения не выглядят хорошо по какой-то причине, вы все еще можете использовать этот старый добрый workaround представления с задержкой 0, как это:

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

хотя я не видел документальной гарантии того, что ваш VC будет находиться в иерархии представлений в блоке отправки времени, запланированном к выполнению, я заметил, что он будет работать просто отлично.

использование задержки, например, 0,2 сек также является опцией. И самое главное - таким образом, вам не нужно возиться с булевой переменной в viewDidAppear:

Это случилось со мной, что segue в раскадровке был какой-то сломанный. Удаление segue (и создание того же самого segue снова) решило проблему.

С вашим главным окном, вероятно, всегда будут времена с переходами, которые несовместимы с представлением предупреждения. Чтобы разрешить представление оповещений в любое время жизненного цикла приложения, необходимо иметь отдельное окно для выполнения задания.

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

основное использование, которое, по замыслу, всегда будет успешным:

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

У меня тоже была эта проблема, но она не имела никакого отношения к срокам. Я использовал синглтон для обработки сцен, и я установил его в качестве ведущего. Другими словами," Я " не было подключено ни к чему. Я просто сделал свою внутреннюю "сцену" новым ведущим и вуаля, это сработало. (Вуаля теряет свое прикосновение после того, как вы узнаете его значение, Хе).

Так что да, речь идет не о "волшебном поиске правильного пути", а о понимании того, где находится ваш код и что он делает. Я рад, что Яблоко дал такое простое английское предупреждение, даже с эмоциями к нему. Слава разработчику apple, который это сделал!!

я исправил это путем перемещения

к сожалению, принятое решение не сработало для моего случая. Я пытался перейти к новому контроллеру Вида сразу после раскрутки от другого контроллера вида.

Я нашел решение, используя флаг, чтобы указать, какой разматывать сегмент был вызван.

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

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

затем представьте разыскиваемый VC с 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
    }
}

В принципе, приведенный выше код позволит мне поймать размотку от входа / регистрации VC и перейти к панели мониторинга, или поймать действие размотки от забыл пароль ВК и перейдите на страницу входа.

такое предупреждение может означать, что вы пытаетесь представить новый View Controller через Navigation Controller при этом Navigation Controller в настоящее время представляет другой View Controller. Чтобы исправить это, вы должны уволить в настоящее время представлены View Controller сначала и по завершении представить новую. Другой причиной предупреждения может быть попытка представить View Controller на потоке другой чем main.

в моей ситуации я не смог поместить свой в переопределение класса. Итак, вот что я получил:

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