Спроба представити UIViewController на UIViewController, перегляд якого відсутній у ієрархії вікон


599

Тільки-но почав використовувати Xcode 4.5, і я отримав цю помилку в консолі:

Попередження: Спроба представити <закінчитиViewController: 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на навігаційний контролер. Ви робите це в делегаті програми?
tarnfeld

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

Той самий випуск частини коду, яка завжди працювала до використання Xcode 4.5, я представляю UINavigationController, але знову це працювало раніше.
Emanuele Fumagalli

У мене така ж проблема, не вирішена. Зробити це за допомогою делегата програми та rootviewcontroller, який викликає "presentViewController", підтримує UITabBarController.
darksider

3
також, якщо виклик цього методу перед викликом makeKeyAndVisible, перемістіть його після цього
mike_haney

Відповіді:


1296

Звідки ви називаєте цей метод? У мене виникла проблема, коли я намагався представити контролер модального перегляду в межах viewDidLoadметоду. Для мене рішенням було перенести цей виклик на viewDidAppear:метод.

Моя припущення полягає в тому, що подання контролера перегляду знаходиться не в ієрархії перегляду вікна в момент завантаження (коли viewDidLoadповідомлення надсилається), але знаходиться в ієрархії вікон після його подання (коли viewDidAppear:повідомлення надсилається) .


Обережність

Якщо ви зателефонуєте presentViewController:animated:completion:в, viewDidAppear:ви можете зіткнутися з проблемою, в якій контролер модального перегляду завжди відображається кожного разу, коли з'являється перегляд контролера перегляду (що має сенс!), І тому представлений контролер модального перегляду ніколи не згасне. .

Можливо, це не найкраще місце для подання контролера модального перегляду, або, можливо, потрібно зберегти якийсь додатковий стан, який дозволяє представленому контролеру подання рішення вирішувати, чи повинен він подавати контролер модального перегляду негайно.


6
@James Ви маєте рацію, перегляд, очевидно, не перебуває в ієрархії, поки після того, як viewWillAppear буде вирішено і після виклику viewDidAppear. Якби це було моїм питанням, я би прийняв цю відповідь;)
Метт Мак,

6
@james Дякую Використання ViewDidAppear вирішило проблему і для мене. Має сенс.
Алі

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

7
Зауважте, що при зміні VC у viewDidAppear це спричиняє виконання виразки з Animation. Викликає спалах / відображення фону.
Вінсент

2
Так, фокус полягає у перегляді viewWillAppear: (BOOL) як анімоване. Ще одна важлива річ, яку ви повинні назвати супер у методі, як [super viewDidAppear: анімований]; без цього це не працює.
BootMaker

66

Ще одна потенційна причина:

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

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


4
У мене була така ж помилка, і ваша відповідь тут допомогла мені зрозуміти, що відбувається, це справді була ця помилка, виправлено її через вас, сер, дякую, +1
самоура

1
Я видалив старий сейг і підключив VC до VC. Чи є спосіб підключити кнопку до панелі розповідей до ВК, оскільки такий спосіб просто помиляється для мене?
MCB

У мене була така ж помилка, ваша відповідь вирішила мою проблему, дякую за вашу увагу. З повагою.
iamburak

1
хаха, випадково я також створював два VC: з кнопки та performSegue, дякую за пораду !!!
Борж

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, і коли я перейду видалити його з батьківського, він не піде. Лололол. Досі застряг без виправлення
Logicsaurus Rex

Працює в Swift3.1
Кан Бюль

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

14

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

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

Тому я не можу використовувати такий код:

[self presentViewController:]

Отже, я отримую UIViewController:

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

Чомусь (логічна помилка), rootViewControllerце щось інше, ніж очікувалося (нормальне UIViewController). Потім я виправляю помилку, замінюючи rootViewControllerна a UINavigationController, і проблема не зникає.


8

TL; DR Ви можете мати лише 1 rootViewController та найновіший представлений. Тому не намагайтеся, щоб контролер перегляду подав інший вигляд, якщо він уже представлений, той, який не був відхилений.

Зробивши кілька власних тестів, я прийшов до висновку.

Якщо у вас є rootViewController, який ви хочете представити все, то ви можете зіткнутися з цією проблемою.

Ось мій код rootController (відкритим є мій ярлик для подання виду контролера з кореня).

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

Якщо я закликаю відкрити двічі поспіль (незалежно від часу, що минув), це буде спрацьовувати чудово на першому відкритому, а НЕ на другому відкритому. Друга відкрита спроба призведе до помилки вище.

Однак якщо я закриваю останнє представлене подання, то дзвінок відкритий, він спрацьовує чудово, коли я знову закликаю відкрити (на іншому контролері перегляду).

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

Я зробив висновок про те, що rootViewController лише MOST-RECENT-CALL знаходиться в Ієрархії перегляду (навіть якщо ви не відхилили його чи не видалили подання). Я спробував грати з усіма дзвінками навантажувача (viewDidLoad, viewDidAppear і робити зворотні відправки), і я виявив, що єдиний спосіб, коли я міг би змусити його працювати, ТІЛЬКИ викликає подарунок від самого контролера самого перегляду.


Це здається набагато більш поширеним питанням, згідно з яким багато відповідей, які визнали ваш. На жаль, це було надзвичайно корисно
rayepps

так, все добре і добре, але яке рішення ... у мене є фонова робоча нитка, яка йде на сервер і відображає розклади ліворуч праворуч і по центру, і вся методологія є хибною .. це жахливо .. що має бути вітерцем, це абсолютно жарт, тому що al я хочу зробити int він фоновий потік: 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

У мене було те саме питання. Проблема полягала в тому, що PerformSegueWithIdentifier був ініційований сповіщенням, як тільки я поклав сповіщення на головний потік, попереджувальне повідомлення пішло.


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, що не має інтерфейсу користувача, яка відображає власний інтерфейс користувача через ваш додаток у певних (нерозкритих) випадках.
igraczech

Ви ЗБЕГАТИ і спалите, якщо хтось вирішить вставити вам sdk у додаток із розширенням. Передайте UIViewController для зловживання вами методом sdk init [s].
Антон Тропашко

Ти справжній Антон. Цей код був написаний, коли розширень не існувало і SDK ще не використовується в жодному з них. Я додав запобіжну пропозицію, щоб пропустити цей кращий регістр.
igraczech

3

Таке попередження може означати , що Ви намагаєтеся представити нові View Controllerнаскрізний в Navigation Controllerтой час як це Navigation Controllerв даний час являє інше View Controller. Щоб виправити це, ви повинні відхилити представлений View Controllerна початку та на завершення подати новий. Ще однією причиною попередження може бути спроба подати View Controllerна потоці іншу, ніж main.


2

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

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

Start містить два дзвінки на self.present()один для UINavigationController та на інший для a UIImagePickerController.

Це зафіксувало це для мене.


2

У мене був подібний випуск у Swift 4.2, але мій погляд не був представлений із циклу перегляду. Я виявив, що у мене було представлено декілька segue одночасно. Тому я використав 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)
  }
}

1

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


1

Доведеться писати нижче рядка.

self.searchController.definesPresentationContext = true

замість

self.definesPresentationContext = true

в UIViewController


1

За допомогою Swift 3 ...

Ще однією можливою причиною цього, що трапилася зі мною, було те, що я почав переходити з таблиціViewCell в інший 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

На жаль, прийняте рішення не спрацювало для моєї справи. Я намагався перейти до нового контролера перегляду відразу після того, як відкрутився від іншого контролера перегляду.

Я знайшов рішення, використовуючи прапор, щоб вказати, який розмотаний мозок викликався.

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

В основному, вищевказаний код дозволить мені зафіксувати розмотування з входу / входу VC та перейти на панель приладів, або здійснити дію розкручування із забутого пароля VC та перейти на сторінку входу.


1

Я виправив цю помилку, зберігаючи верхній самий вигляд контролера в постійний, який знаходимо протягом циклу над 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];    
});

Хоча я не бачив жодної документально підтвердженої гарантії того, що ваш ВК буде знаходитись в ієрархії подання блоку часу, коли планується виконання блоку відправки часу, я помітив, що це буде добре.

Використання затримки, наприклад, 0,2 сек. Також є варіантом. І найкраще - таким чином вам не потрібно возитися з булевою змінною вviewDidAppear:


2
Хоча це може працювати і зараз, немає жодної гарантії, що Apple в майбутньому змінить поведінку і порушить це на вас. Потім, коли ви повернетесь, щоб переглянути свій код, щоб спробувати виправити речі ще раз, вам буде цікаво, чому ви робили цю, здавалося б, непотрібну відправлення.
Крінх

Відправлення з 0 разів врятувало мене вже досить багато разів - іноді речі, які просто повинні логічно працювати, зазвичай не працюють без цього. Тому просто коментуйте собі та іншим те, чому ви робите непомітні речі (не лише така відправка), і вам слід добре.
Dannie P

2
Ви просто хакеруєте навколо проблеми, а не вирішуєте її.
Майкл Петерсон

0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.