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


599

刚开始使用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];

3
除了尝试调用presentViewController:animated:completion导航控制器外,我遇到了完全相同的问题。您是在应用程序委托中执行此操作吗?
塔恩费尔德,2012年

不,我正在从一个视图控制器到另一个视图控制器。您找到任何解决方案了吗?
凯尔·戈斯兰

在使用Xcode 4.5之前始终起作用的部分代码上存在相同的问题,我提出了一个UINavigationController,但同样在以前一直起作用。
Emanuele Fumagalli 2012年

我有同样的问题,没有解决。从应用程序委托执行此操作,然后rootviewcontroller调用UITabBarController调用“ presentViewController”。
darksider 2012年

3
另外,如果在调用makeKeyAndVisible之前先调用此方法,请在此之后移动它
mike_haney 2012年

Answers:


1296

您从哪里调用此方法?我在尝试在方法中呈现模态视图控制器时遇到问题viewDidLoad。对我来说,解决方案是将此调用移至该viewDidAppear:方法。

我的假设是视图控制器的观点是不是在窗口的视图层次的,它已经加载点(当viewDidLoad发送邮件),但它在窗口层次结构已经呈现后(当viewDidAppear:发送消息) 。


警告

如果您确实致电presentViewController:animated:completion:,则viewDidAppear:可能会遇到一个问题,即只要出现视图控制器的视图,便总是显示模态视图控制器(这很有意义!),因此所呈现的模态视图控制器将永远不会消失。 。

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


6
@詹姆斯你是正确的,认为显然不是在层次结构,直到 viewWillAppear中得到解决,一旦viewDidAppear被调用。如果这是我的问题,我将接受这个答案;)
Matt Mc

6
@james谢谢。使用ViewDidAppear也为我解决了这个问题。说得通。
阿里

44
我希望我可以投票两次。我只是遇到了这个问题,来到线程中发现我上次看到此内容时已经赞成。
Schrockwell

7
请注意,当您在viewDidAppear中更改VC时,这将导致通过动画执行segue。导致背景的闪烁/显示。
文森特2013年

2
是的,技巧是正确设置viewWillAppear:(BOOL)动画。您还必须在方法中调用super,这很重要,例如[super viewDidAppear:animated]; 没有这个就行不通。
BootMaker 2013年

66

另一个潜在原因:

当我不小心两次出现相同的视图控制器时,我遇到了这个问题。(一次performSegueWithIdentifer:sender:在按下按钮时被调用,第二次将segue直接连接到按钮)。

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


4
我遇到了同样的错误,您在这里的回答帮助我弄清楚了是怎么回事,确实是这个错误,是由于您先生而修复的,谢谢您,+ 1
samouray 2015年

1
我删除了旧的segue并将VC连接到VC。有没有一种方法可以将按钮连接到StoryBoard并连接到VC,因为那样只会对我造成错误?
MCB

我也遇到了同样的错误,您的回答解决了我的问题,感谢您的关注。亲切的问候。
iamburak '16

1
大声笑,我偶然也创建了两个vc:from button和performSegue,谢谢你的提示!!!
Borzh

1
就我而言,我是present(viewController, animated: true, completion: nil)在循环内调用。
萨摩

38

viewWillLayoutSubviewsviewDidLayoutSubviews(iOS 5.0+)可以用于此目的。它们的调用早于viewDidAppear。


3
仍然在其他场合也使用它们,因此我认为在视图的“生命周期”中可能会多次调用它们。
强尼

这也不是方法的目的-顾名思义。viewDidAppear是正确的。阅读视图生命周期是一个好主意。
使用者2013年

这是最好的解决方案。就我而言,在viewDidAppear中呈现会导致在加载模式之前瞬间显示视图控制器,这是不可接受的。
TMilligan

尝试显示警报时,此答案对我而言效果最好。将警报放入viewDidLoad和viewWillAppear中时不会显示警报。
uplearnedu.com 2016年

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

当我尝试呈现UIViewControllerin 时,我也遇到了这个问题viewDidLoad。詹姆斯·贝德福德(James Bedford)的答案奏效,但我的应用程序先显示背景1或2秒。

经过研究,我找到了一种使用来解决此问题的方法addChildViewController

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

9
我认为您缺少[navigationViewController didMoveToParentViewController:self]
foFox 2013年

2
我尝试了您的代码以及foFox的建议,当我将其从父级中删除时,它不会消失。哈哈 仍然无法解决。
Logicsaurus Rex 2016年

在Swift3.1中工作
Kang Byul

@sunkehappy在presentviewcontroller之前要在上面两行使用,但是它为什么崩溃了?
Iyyappan Ravi

14

可能像我一样,你有错误的根源 viewController

我想ViewControllernon-UIViewController上下文中显示

所以我不能使用这样的代码:

[self presentViewController:]

因此,我得到一个UIViewController:

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

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


8

TL; DR您只能有1个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在视图层次结构上(即使您没有关闭它或删除视图)。我尝试使用所有加载程序调用(viewDidLoad,viewDidAppear和进行延迟的分派调用),但我发现使它起作用的唯一方法是仅从最顶层的视图控制器调用present。


这似乎是一个更为普遍的问题,许多答案已将您的问题投了票。不幸的是,这非常有帮助
rayepps

是的,一切都很好,但是解决方案是什么...我有一个后台工作线程去服务器,并在左右和中间显示情节提要,整个方法是虚假的..太糟糕了。开个玩笑,因为我想做的是他的后台线程是:wait wait decide push screen to frontIOS是不可能的?
Heelis先生,

5

我的问题是,我在调用窗口之前UIApplicationDelegate正在使用的didFinishLaunchingWithOptions方法执行segue makeKeyAndVisible()


怎么样?你能详细说明吗?我面临着同样的问题。这是我的代码,让initialViewControlleripad:UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier:“ SplashController”)作为UIViewController self.window?.rootViewController = initialViewControlleripad self.window?.makeKeyAndVisible()
shahtaj khalid

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

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

因此,总而言之,无论您在做什么,都要确保该模式不会被多次调用


3

考虑到您想在viewController几乎任何地方显示一些代码,我最终得到了这样的代码,最终对我有用(Swift)。当没有可用的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
        })
    }
}

顺便说一句,我该从哪里调用此方法...这是另外一个没有UI的SDK库,在某些情况下(未公开),它会在您的应用程序上显示其自己的UI。
igraczech '16

如果有人决定将您的SDK嵌入具有扩展名的应用程序中,您将崩溃并烧毁。将UIViewController传递给您的sdk init方法。
安东·特罗帕斯科

你是真正的安东。当扩展名不存在并且在所有扩展名中都未使用SDK时,将编写此代码。我添加了保护子句以跳过这种极端情况。
igraczech '16

3

这种警告可能意味着您正在尝试提出新的提示View ControllerNavigation ControllerNavigation Controller目前正在提出另一个提示View Controller。要解决它,您必须先解散当前显示的内容View Controller,完成后再显示新的内容。导致警告的另一个原因可能是试图View Controller在线程上出现而不是main


2

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

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

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

这为我解决了。


2

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

1

当从嵌入在容器中的视图控制器执行segue时,您也可以收到此警告。正确的解决方案是从容器的父级而不是从容器的视图控制器使用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

我碰巧,情节提要中的故事被打破了。删除segue(然后再次创建完全相同的segue)可以解决此问题。


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。

我找到了一个解决方案,方法是使用一个标志来指示调用了哪个缓动序列。

@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

我通过将最顶级的viewcontroller存储为常量来解决此错误,该常量在rootViewController的while周期内找到:

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。展开视图控制器后,以编程方式直接启动segue时发生了问题。

解决此问题的同时修复了一个相关的错误,即用户现在可以通过向下滑动页面来消除干扰。这破坏了我程序的逻辑。

通过将所有视图控制器上的“演示”模式从“ 自动”更改为“全屏”,可以修复此问题

您可以在界面构建器的属性面板中执行此操作。或参阅此答案以了解如何以编程方式进行操作。


0

我也有这个问题,但是与时间无关。我当时使用单例来处理场景,因此将其设置为演示者。换句话说,“自我”与任何事物都不相关。我只是将其内部的“场景”设置为新的演示者,瞧,它起作用了。(Voila在了解其含义后会失去联系,呵呵)。

是的,这不是关于“神奇地找到正确的方式”,而是关于了解代码的位置及其作用。我很高兴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
尽管这现在可能行得通,但不能保证苹果将来会改变行为并破坏您的行为。然后,当您再次查看代码以尝试再次修复问题时,您会想知道为什么要执行此看似不必要的调度。
克鲁尼2015年

零时间调度已经为我节省了很多时间-有时候,应该从逻辑上讲应该正常工作的东西如果没有它通常是行不通的。因此,只为自己和其他人提出评论,说明为什么您要做不明显的事情(不仅是这样的调度),而且应该没问题。
丹妮·P

2
您只是在解决问题,而不是解决问题。
迈克尔·彼得森

0

如果您有可用的导航控制器,则可用于显示任何视图控制器。 self.navigationController?.present(MyViewController,动画:true,完成:nil)另外,我还可以显示警报和邮件控制器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.