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


599

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

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

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

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

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

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

3
У меня точно такая же проблема, за исключением попытки вызвать presentViewController:animated:completionнавигационный контроллер. Вы делаете это в приложении делегата?
Тарнфельд

Нет, я делаю это от одного контроллера представления к другому. Вы нашли какие-нибудь решения?
Кайл Гослан

Та же проблема с частью кода, которая всегда работала до использования Xcode 4.5, я представляю UINavigationController, но опять же, это всегда работало раньше.
Эмануэле Фумагалли

У меня такая же проблема, не решенная. Делая это из делегата приложения, и rootviewcontroller, вызывающий «presentViewController», является UITabBarController.
darksider

3
Кроме того, если вызов этого метода перед вызовом makeKeyAndVisible, переместите его после этого
mike_haney

Ответы:


1296

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

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


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

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

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


6
@James вы правильно, по- видимому , вид не является в иерархии до тех пор , после того, как viewWillAppear была решена и когда viewDidAppear был вызван. Если бы это был мой вопрос, я бы принял этот ответ;)
Мэтт Мак

6
@james Спасибо. Использование ViewDidAppear решило проблему и для меня. Имеет смысл.
Али

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

7
Обратите внимание, что при изменении VC в viewDidAppear это вызывает выполнение перехода с анимацией. Вызывает вспышку / отображение фона.
Винсент

2
Да, уловка заключается в том, что viewWillAppear: (BOOL) анимированный, как это правильно. Еще одна важная вещь, которую вы должны назвать super в методе, как [super viewDidAppear: animated]; без этого это не работает.
BootMaker

66

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

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

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


4
У меня была та же ошибка, и ваш ответ здесь помог мне понять, что происходит, это была действительно эта ошибка, исправленная из-за вас, сэр, спасибо, +1
samouray

1
Я удалил старый segue и подключил VC к VC. Есть ли способ подключить кнопку к storyBoard к ВК, потому что этот способ постоянно вызывает у меня ошибку?
MCB

У меня была та же ошибка, ваш ответ решил мою проблему, спасибо за ваше внимание. С уважением.
Ямбурак

1
LOL, случайно я также создавал два VC: от кнопки и выполнить сегмента, спасибо за совет !!!
Борж

1
В моем случае я звонил present(viewController, animated: true, completion: nil)внутри цикла.
Само

38

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


3
Тем не менее, они используются и в других случаях, поэтому я думаю, что их можно вызывать несколько раз в «жизни» представления.
Джонни

Это также не то, для чего методы - как следует из названия. viewDidAppear правильно. Читать о жизненном цикле представления - хорошая идея.
Толуизер

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

Этот ответ работал лучше всего для меня при попытке отобразить предупреждение. Предупреждение не будет отображаться, когда я помещу его в viewDidLoad и viewWillAppear.
uplearnedu.com

26

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

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

Работал для меня также
Азиз Джавед

17

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

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

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

9
Я думаю, что вам не хватает [navigationViewController didMoveToParentViewController: self]
foFox

2
Я попробовал ваш код вместе с предложением foFox, и когда я собираюсь удалить его из родительского, он не исчезнет. Lololol. Все еще застрял без исправления.
Логиксавр Рекс

Работает в Swift3.1
Кан Бюль

@sunkehappy над двумя строками, которые будут использоваться перед presentviewcontroller, но его сбой почему?
Iyyappan Ravi

14

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

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

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

[self presentViewController:]

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

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

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


8

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


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

да, все хорошо, но каково решение ... у меня есть фоновый рабочий поток, идущий на сервер и показывающий раскадровки слева направо и в центре, и вся методология является фиктивной ... это ужасно ... то, что должно быть бризом, совершенно шутка, потому что все, что я хочу сделать в фоновом потоке: wait wait decide push screen to frontи это невозможно, это IOS ????
Мистер

5

Мой вопрос я выполнял SEGUE в UIApplicationDelegate«S didFinishLaunchingWithOptionsметода , прежде чем я позвонил makeKeyAndVisible()в окне.


как? можешь уточнить? Я сталкиваюсь с той же проблемой. это мой код пусть initialViewControlleripad: UIViewController = mainStoryboardIpad.instantiateViewController (withIdentifier: "SplashController") , как UIViewController self.window .rootViewController = initialViewControlleripad self.window .makeKeyAndVisible ()?
shahtaj Халид

4

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

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

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

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

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


Я имею в виду, что видео должно быть остановлено / приостановлено первым.
Влад

на самом деле это помогло мне, оно привело меня к этому stackoverflow.com/questions/20746413/…
Омар Н Шамали

3

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


3

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

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

3

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

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


3

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

Кстати, чтобы понять, ГДЕ я вызываю этот метод из ... это библиотека SDK без UI, которая в определенных (не раскрытых) случаях отображает свой собственный интерфейс над вашим приложением.
igraczech

Вы БУДЕТЕ терпеть крах и сгорать, если кто-то решит встроить ваш SDK в приложение с расширением. Передайте UIViewController для злоупотребления в ваш метод SDK init [s].
Антон Тропашко

Ты настоящий Антон. Этот код был написан, когда Расширения не существовали, и SDK еще не использовался ни в одном из них. Я добавил пункт охраны, чтобы пропустить этот крайний случай.
igraczech

3

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


3

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

2

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

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

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

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


1

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


1

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

self.searchController.definesPresentationContext = true

вместо

self.definesPresentationContext = true

в UIViewController


1

С Swift 3 ...

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

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


1

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

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


1

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


1

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

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

1

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

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

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

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

Затем представьте желаемый ВК с 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 Забыли пароль и перейти на страницу входа.


1

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

Я обнаружил, что эта ошибка появилась после обновления XCode, я верю в Swift 5 . Проблема возникла, когда я программно запустил переход непосредственно после размотки контроллера представления.

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

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

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


0

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

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


0

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


2
Хотя это может сработать сейчас, нет никаких гарантий, что Apple изменит поведение и нарушит это в будущем. Затем, когда вы вернетесь к своему коду, чтобы попытаться исправить ситуацию, вы удивитесь, почему вы выполняли эту, казалось бы, ненужную диспетчеризацию.
Cruinh

Отправка с 0 раз уже спасла меня довольно много раз - иногда вещи, которые просто должны логически работать, обычно не работают без него. Так что просто комментируйте для себя и других, почему вы делаете неочевидные вещи (не только такую ​​рассылку), и у вас все будет хорошо.
Дэнни П

2
Вы просто взламываете проблему и не решаете ее.
Майкл Петерсон

0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.