首页 文章

尝试在UIViewController上呈现UIViewController,其视图不在窗口层次结构中

提问于
浏览
510

刚开始使用Xcode 4.5,我在控制台中遇到了这个错误:

警告:尝试在<ViewController:0x1ec3e000>上显示<finishViewController:0x1e56e0a0>,其视图不在窗口层次结构中!

该视图仍在呈现,应用程序中的所有内容都正常运行 . 这是iOS 6中的新功能吗?

这是我用于在视图之间切换的代码:

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

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

27 回答

  • -1

    我最终得到了这样一个代码,最终对我有用(Swift),考虑到你想从几乎任何地方显示一些 viewController . 当没有rootViewController可用时,此代码显然会崩溃,这是开放式结尾 . 它也不包括通常需要切换到UI线程使用

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

    要显示主视图的任何子视图,请使用以下代码

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

    你在哪里调用这种方法?我有一个问题,我试图在 viewDidLoad 方法中呈现模态视图控制器 . 我的解决方案是将此调用移至 viewDidAppear: 方法 .

    我的假设是视图控制器在窗口的视图层次结构中查看 is not 它已被加载的点(当发送 viewDidLoad 消息时),但它在窗口层次结构中显示 is (当发送 viewDidAppear: 消息时) ) .


    Caution

    如果您确实在 viewDidAppear: 中调用 presentViewController:animated:completion: ,则可能会遇到一个问题,即每当视图控制器的视图出现时,模态视图控制器总是被呈现(这是有意义的!),因此呈现的模态视图控制器永远不会消失...

    也许这不是呈现模态视图控制器的最佳位置,或者可能需要保留一些额外的状态,这允许呈现视图控制器决定它是否应该立即呈现模态视图控制器 .

  • 1

    With Swift 3...

    发生在我身上的另一个可能原因是,从一个tableViewCell到Storyboard上的另一个ViewController有一个segue . 单击单元格时,我也使用了 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {} .

    我通过从ViewController到ViewController制作一个segue来解决这个问题 .

  • 1

    它发生在我身上 segue in the storyboard was some kind of broken . 删除segue(并再次创建完全相同的segue)解决了这个问题 .

  • 13

    可悲的是,接受的解决方案对我的案子不起作用 . 我正试图从另一个视图控制器退出后立即导航到新的视图控制器 .

    我找到了一个解决方案,使用一个标志来指示调用了哪个展开segue .

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

    然后用 present(_ viewControllerToPresent: UIViewController) 呈现想要的VC

    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

    使用主窗口时,可能总会出现与显示警报不兼容的转换 . 为了允许在应用程序生命周期中随时显示警报,您应该有一个单独的窗口来完成工作 .

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

    如果您有播放视频的AVPlayer对象,则必须先暂停视频 .

  • 17

    如果其他解决方案由于某种原因看起来不太好,你仍然可以使用这个好的旧 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: 中弄乱布尔变量

  • 5

    在我的情况下,我无法将其置于类覆盖中 . 所以,这是我得到的:

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

    必须写下面的行 .

    self.searchController.definesPresentationContext = true
    

    代替

    self.definesPresentationContext = true
    

    在UIViewController中

  • 3

    我遇到过同样的问题 . 问题是,performSegueWithIdentifier是由通知触发的,只要我在主线程上发出通知警告消息就消失了 .

  • 1

    从嵌入容器的视图控制器执行segue时,也可以收到此警告 . 正确的解决方案是使用来自容器父级的segue,而不是容器的视图控制器 .

  • 4

    我的问题是我在窗口上调用 makeKeyAndVisible() 之前在 UIApplicationDelegatedidFinishLaunchingWithOptions 方法中执行了segue .

  • 0

    我也有这个问题,但它与时间无关 . 我正在使用单例处理场景,我将其设置为演示者 . 换句话说,“自我”并没有被任何东西所吸引 . 我只是让它的内心“场景”成为新的主持人而且瞧,它奏效了 . (Voila在你了解它的含义后失去了它的触觉,呵呵) .

    所以是的,这不是“神奇地找到正确的方法”,它是关于理解代码所处的位置以及它正在做什么 . 我很高兴苹果公司给出了这样一个简单的英语警告信息,即使对此情有独钟 . 感谢苹果开发者做到了!

  • 1

    viewWillLayoutSubviewsviewDidLayoutSubviews (iOS 5.0)可用于此目的 . 它们比viewDidAppear更早被调用 .

  • 5

    我有同样的问题 . 我必须嵌入一个导航控制器并通过它呈现控制器 . 以下是示例代码 .

    - (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
    
    }
    
  • 2

    我已经尝试了很多答案,但没有人帮助我唯一能为我工作的解决方案是快速4解决方案<ViewController:0x1ec3e000>其视图不在窗口层次结构中!

    override func viewWillAppear(_ animated: Bool) {
        let appDelegate = UIApplication.shared.delegate as? AppDelegate
      appDelegate?.window?.rootViewController  = self
    
    }
    
  • 3

    TL;DR 您只能拥有1个rootViewController及其最近提供的rootViewController . 因此,已经提出了一个尚未被解雇的人 .

    在做了一些我自己的测试后,我得出了一个结论 .

    如果你有一个想要呈现所有内容的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(在另一个viewcontroller上)时它可以正常工作 .

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

    我得出的结论是,只有MOST-RECENT-CALL的rootViewController在视图Hierarchy上(即使你没有关闭它或删除一个视图) . 我尝试使用所有加载器调用(viewDidLoad,viewDidAppear,并执行延迟调度调用),我发现只有这样才能让它工作才能从最顶层的视图控制器调用present .

  • 1105

    当我试图在 viewDidLoad 中呈现视图控制器时,我也遇到了这个问题 . 我_343217的答案 . 它可以工作,但我的应用程序将首先显示背景1或2秒 .

    经过研究我终于找到了解决这个问题的另一种方法是使用子视图控制器 .

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

    它工作正常试试这个 . Link

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

    另一个潜在原因:

    当我意外地两次呈现相同的视图控制器时,我遇到了这个问题 . (一次是按下按钮时调用的 performSegueWithIdentifer:sender: ,第二次是直接连接按钮的segue) .

    实际上,两个segues同时开火,我得到了错误: Attempt to present X on Y whose view is not in the window hierarchy!

  • 2

    万一它对任何人都有帮助,我的问题非常愚蠢 . 完全是我的错 . 通知正在触发调用模态的方法 . 但我没有正确删除通知,因此在某些时候,我会有多个通知,因此模式将被多次调用 . 当然,在您调用模态一次之后,调用它的视图控制器不再在视图层次结构中,这就是我们看到此问题的原因 . 正如您所料,我的情况也引发了许多其他问题 .

    总而言之,无论你做什么 make sure the modal is not being called more than once .

  • 1

    可能和我一样,你的根本错了 viewController

    我想在 non-UIViewController 上下文中显示 ViewController

    So I can't use such code:

    [self presentViewController:]
    

    So, I get a UIViewController:

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

    出于某种原因(逻辑错误), rootViewController 不是预期的(正常的 UIViewController ) . 然后我更正了错误,用 UINavigationController 替换 rootViewController ,问题就消失了 .

  • 0

    我通过移动 dismiss 完成块内的 start() 函数来修复它:

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

    Start包含两个对 self.present() 的调用,一个用于UINavigationController,另一个用于 UIImagePickerController .

    那为我修好了 .

  • 55

    这种警告可能意味着你正试图通过 Navigation Controller 呈现新的 View Controller ,而 Navigation Controller 目前正在呈现另一个 View Controller . 要修复它你必须首先解除当前显示的 View Controller ,并在完成时显示新的 . 警告的另一个原因可能是尝试在 View Controller 之外的线程上显示 View Controller .

  • 1

    我有这个问题,根本原因是多次订阅按钮点击处理程序(TouchUpInside) .

    它是在ViewWillAppear中订阅的,因为我们已添加导航到另一个控制器,然后展开回到它,因此被多次调用 .

相关问题