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

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

Предупреждение: попытка представить < finishViewController: 0x1e56e0a0 > on < ViewController: 0x1ec3e000 > чей вид не находится в иерархии окон!

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

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

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

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

Ответ 1

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

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


Внимание

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

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

Ответ 2

Другая потенциальная причина:

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

Я хочу отобразить viewController в контексте non-UIViewController,

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

[self presentViewController:]

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

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

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

Ответ 7

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

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

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

Вот мой код rootController (open - это мой ярлык для представления диспетчера представлений из корня).

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 (на другом диспетчере представлений).

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

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

Ответ 12

Хорошо работает. Ссылка

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

Ответ 13

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

Ответ 14

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

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

Ответ 15

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

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

}

Ответ 16

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

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

Ответ 17

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

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

В начало содержит два вызова self.present() один для UINavigationController и еще один для UIImagePickerController.

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

Ответ 18

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

Ответ 19

Приходится писать ниже строки.

self.searchController.definesPresentationContext = true

вместо

self.definesPresentationContext = true

в UIViewController

Ответ 20

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

Ответ 21

С Swift 3...

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

Я исправил эту проблему, сделав segue из ViewController в ViewController.

Ответ 22

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

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

Ответ 23

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

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

Ответ 24

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

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

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

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

Затем представить present(_ viewControllerToPresent: UIViewController) 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
    }
}

По сути, приведенный выше код позволит мне перехватить раскрутку с помощью Login/SignUp VC и перейти к панели инструментов, или перехватить действие раскрутки с помощью пароля VC Забыли пароль и перейти на страницу входа.

Ответ 25

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

Так что да, это не о "волшебном поиске правильного пути", о понимании того, где стоит ваш код и что он делает. Я счастлив, что Apple дала такое простое английское предупреждающее сообщение, даже с волнением. Престижность к яблочному dev, который сделал это!

Ответ 26

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

Ответ 27

У меня была похожая проблема на 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)
  }
}

Ответ 28

Это работает для представления любого контроллера представления, если у вас есть доступный контроллер навигации. self.navigationController?.present(MyViewController, анимированный: true, завершение: nil) Также я могу представить оповещения и почтовый контроллер.