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

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

Предупреждение. Попытайтесь представить в , чье представление не находится в иерархии окон!

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

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

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

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

39 ответов

Решение

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

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


предосторожность

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

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

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

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

По сути, два segues были запущены одновременно, и я получил ошибку: 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и проблема исчезла.

Swift 5 - фоновый поток

Если контроллер предупреждений выполняется в фоновом потоке, может возникнуть ошибка «Попытка представить ... чье представление не входит в иерархию окон».

Итак, это:

      present(alert, animated: true, completion: nil)
    

Было исправлено с этим:

      DispatchQueue.main.async { [weak self] in
    self?.present(alert, animated: true, completion: nil)
}

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

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

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

Вот мой код rootController (open - мой ярлык для представления 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 только MOST-RECENT-CALL находится в иерархии представления (даже если вы не отклонили его или не удалили представление). Я пытался играть со всеми вызовами загрузчика (viewDidLoad, viewDidAppear и делать отложенные вызовы диспетчеризации) и обнаружил, что единственный способ заставить его работать - это ТОЛЬКО вызов подарка с самого верхнего контроллера представления.

У меня была аналогичная проблема в Swift 4.2, но мое мнение не было представлено в цикле просмотра. Я обнаружил, что мне нужно было показать несколько переходов одновременно. Поэтому я использовал 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)
  }
}

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

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

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

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

override func viewDidLoad() {
    super.viewDidLoad()
    OperationQueue.main.addOperation {
        // push or present the page inside this block
    }
}

Я исправил эту ошибку, сохранив самый верхний контроллер представления в константе, которая находится внутри цикла во время цикла над 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
}

Я закончил с таким кодом, который, наконец, работает для меня (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
        })
    }
}

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

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

}

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

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

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

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

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

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

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

Я исправил это, переместив start() функция внутри dismiss блок завершения:

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

Start содержит два вызова self.present() один для UINavigationController и другой для UIImagePickerController,

Это исправило это для меня.

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

/// 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"];

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

Это случилось со мной, когда я пытался представить свой navigationController, когда его представление еще не было представлено в иерархии представлений. Я решил это, прослушав метод didShow NavigationControllerDelegate. Как только вызывается метод didShow, я знаю, что могу представить его на своем навигационном контроллере.

Примечание: использование dispatchQueueAsync.await(.now()) { //present }действительно работает, но он хакерский и подвержен ошибкам, если представление занимает много времени для отображения в иерархии представлений.

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

self.searchController.definesPresentationContext = true

вместо

self.definesPresentationContext = true

в UIViewController

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

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

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

С Swift 3...

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

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

          let alert = UIAlertController(title: "", message: "YOU SUCCESSFULLY\nCREATED A NEW\nALERT CONTROLLER", preferredStyle: .alert)
    func okAlert(alert: UIAlertAction!)
    {
        
    }
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: okAlert))
    
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    var rootVC = window?.rootViewController
    
    if var topController = rootVC
    {
        while let presentedViewController = topController.presentedViewController
        {
            topController = presentedViewController
        }
        rootVC = topController
    }
    rootVC?.present(alert, animated: true, completion: nil)

Если по каким-то причинам другие решения не выглядят хорошими, вы все равно можете использовать этот старый добрый 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:

Другие вопросы по тегам