647

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

35 回答 35

1381

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

我的假设是视图控制器的视图在加载时(发送消息时)不在窗口的视图层次结构中,但在呈现后(发送消息时)在窗口层次结构中.viewDidLoadviewDidAppear:


警告

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

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

于 2012-09-07T14:36:46.623 回答
70

另一个潜在原因:

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

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

于 2015-04-17T19:56:40.040 回答
38

viewWillLayoutSubviewsviewDidLayoutSubviews(iOS 5.0+) 可用于此目的。它们在 viewDidAppear 之前被调用。

于 2013-02-22T03:10:41.253 回答
29

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

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];
于 2016-03-09T06:22:53.383 回答
19

当我尝试展示一个UIViewControllerin时,我也遇到了这个问题viewDidLoad。詹姆斯贝德福德的回答有效,但我的应用程序首先显示背景 1 或 2 秒。

经过一番研究,我找到了一种使用addChildViewController.

- (void)viewDidLoad
{
    ...
    [self.view addSubview: navigationViewController.view];
    [self addChildViewController: navigationViewController];
    ...
}
于 2012-12-06T05:35:24.370 回答
14

可能和我一样,你有一个错误的根viewController

我想ViewControllernon-UIViewController上下文中显示一个,

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

[self presentViewController:]

所以,我得到了一个 UIViewController:

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

由于某种原因(逻辑错误),这rootViewController是与预期不同的东西(正常UIViewController)。然后我更正了错误,替换rootViewController为 a UINavigationController,问题就消失了。

于 2013-07-24T02:30:33.130 回答
8

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,当我再次调用 open(在另一个视图控制器上)时它工作得很好。

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

我得出的结论是,只有 MOST-RECENT-CALL 的 rootViewController 位于视图层次结构中(即使您没有关闭它或删除视图)。我尝试使用所有加载程序调用(viewDidLoad、viewDidAppear 和延迟调度调用),我发现我可以让它工作的唯一方法是只从最顶层的视图控制器调用 present。

于 2015-08-17T19:48:23.963 回答
7

Swift 5 - 后台线程

如果在后台线程上执行警报控制器,则可能会出现“尝试呈现......其视图不在窗口层次结构中”的错误。

所以这:

present(alert, animated: true, completion: nil)
    

修复了这个:

DispatchQueue.main.async { [weak self] in
    self?.present(alert, animated: true, completion: nil)
}
于 2021-01-05T02:35:16.533 回答
6

我在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)
  }
}
于 2019-09-01T00:38:48.480 回答
5

我的问题是在调用窗口之前我正在执行UIApplicationDelegate's方法中的 segue。didFinishLaunchingWithOptionsmakeKeyAndVisible()

于 2017-05-17T01:59:47.783 回答
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)
}
于 2018-09-29T23:01:30.393 回答
4

您可以调用您的 segues 或在此块内呈现、推送代码:

override func viewDidLoad() {
    super.viewDidLoad()
    OperationQueue.main.addOperation {
        // push or present the page inside this block
    }
}
于 2020-07-22T22:03:19.917 回答
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

}
于 2014-02-20T09:34:52.030 回答
3

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

于 2015-06-18T20:11:26.537 回答
3

我遇到过同样的问题。问题是, performSegueWithIdentifier 是由通知触发的,一旦我将通知放在主线程上,警告消息就消失了。

于 2015-09-09T10:41:17.970 回答
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
        })
    }
}
于 2016-02-22T17:42:19.353 回答
3

它工作正常试试这个。关联

UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:secondView animated:YES completion: nil];
于 2016-05-23T06:37:25.177 回答
3

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

总而言之,无论您在做什么,请确保模态不会被多次调用

于 2016-09-06T13:56:45.883 回答
3

这种警告可能意味着您正在尝试呈现新View Controller的,Navigation ControllerNavigation Controller当前正在呈现另一个View Controller. 要修复它,您必须首先关闭当前呈现View Controller的并在完成时呈现新的。警告的另一个原因可能是尝试View Controller在线程上显示main.

于 2018-06-11T08:33:18.817 回答
2

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

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

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

那为我修好了。

于 2018-05-17T09:23:27.693 回答
1

从嵌入在容器中的视图控制器执行转场时,您也可能会收到此警告。正确的解决方案是使用容器父级的 segue,而不是容器的视图控制器。

于 2015-12-02T21:06:54.160 回答
1

必须写在下面一行。

self.searchController.definesPresentationContext = true

代替

self.definesPresentationContext = true

在 UIViewController

于 2016-02-18T10:17:54.840 回答
1

我碰巧故事板中的segue有点坏了。删除转场(并再次创建完全相同的转场)解决了这个问题。

于 2016-07-29T19:34:35.557 回答
1

随着斯威夫特 3...

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

我通过从 ViewController 到 ViewController 进行 segue 解决了这个问题。

于 2017-01-17T00:30:48.627 回答
1

我遇到了这个问题,根本原因是多次订阅按钮单击处理程序(TouchUpInside)。

它在 ViewWillAppear 中订阅,它被多次调用,因为我们添加了导航以转到另一个控制器,然后展开回到它。

于 2017-03-30T19:25:37.637 回答
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"];
于 2018-01-13T11:39:29.053 回答
1

可悲的是,接受的解决方案不适用于我的情况。从另一个视图控制器展开后,我试图导航到一个新的视图控制器。

我找到了一个解决方案,方法是使用一个标志来指示调用了哪个 unwind segue。

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

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

然后提出想要的VCpresent(_ 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 中捕获展开动作并导航到登录页面。

于 2018-06-07T20:45:36.790 回答
1

我通过将最顶层的视图控制器存储到常量中修复了这个错误,该常量在 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
}
于 2019-10-30T09:30:55.243 回答
1

我发现这个错误是在更新 Xcode 后出现的,我相信Swift 5。当我在展开视图控制器后直接以编程方式启动 segue 时,问题就发生了。

解决方案在修复相关错误的同时出现,即用户现在可以通过向下滑动页面来解除 segues。这打破了我程序的逻辑。

通过将所有视图控制器上的演示模式从Automatic更改为Full Screen来修复它。

您可以在界面生成器的属性面板中执行此操作。或查看此答案以了解如何以编程方式进行操作。

于 2019-12-13T19:00:16.403 回答
1

斯威夫特 5

我将 present inviewDidLayoutSubviews称为presenting inviewDidAppear会导致在加载模式之前视图控制器的瞬间显示,这看起来像一个丑陋的故障

确保检查窗口是否存在并只执行一次代码

var alreadyPresentedVCOnDisplay = false

override func viewDidLayoutSubviews() {
        
    super.viewDidLayoutSubviews()
    
    // we call present in viewDidLayoutSubviews as
    // presenting in viewDidAppear causes a split second showing 
    // of the view controller before the modal is loaded
    
    guard let _ = view?.window else {
        // window must be assigned
        return
    }
    
    if !alreadyPresentedVCOnDisplay {
        alreadyPresentedVCOnDisplay = true
        present(...)
    }
    
}
于 2020-09-09T16:42:46.993 回答
0

我也遇到了这个问题,但和时间无关。我使用单例处理场景,并将其设置为演示者。换句话说,“自我”并没有与任何东西挂钩。我只是把它的内部“场景”变成了新的演示者,瞧,它奏效了。(瞧,在你了解了它的意思后,它就失去了联系,呵呵)。

所以是的,这不是关于“神奇地找到正确的方法”,而是关于理解你的代码在哪里以及它在做什么。我很高兴苹果公司给出了这样一个简单的英语警告信息,即使它带有情感。向这样做的苹果开发人员致敬!

于 2013-09-17T07:48:56.617 回答
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:

于 2014-09-20T12:44:22.813 回答
0

如果您有可用的导航控制器,这适用于呈现任何视图控制器。 self.navigationController?.present(MyViewController, animated: true, completion: nil) 另外,我还可以显示警报和邮件控制器。

于 2019-09-11T02:48:58.427 回答
0

该消息显示为警告,有时代码拒绝工作。(!需要引用:较新的 SDK 可能有严格的规则)。

我遇到它的原因不止一个,主要是复杂的视图控制器场景。这是一个例子。

Scenario: MainViewController (responsible to load: ViewControllerA & ViewControllerB)

呈现ViewControllerAMainViewController不解散ViewControllerA您尝试呈现viewControllerB的对象MainViewController(使用委托方法)。

在这种情况下,您必须确保关闭 ViewControllerA,然后调用 ViewControllerB。

因为在呈现 ViewControllerA 之后(ViewControllerA 负责显示视图和视图控制器,并且当 MainViewController 尝试加载另一个视图控制器时,它拒绝处理并抛出警告)。

于 2020-02-17T08:04:43.723 回答
0

当我试图在我的 navigationController 上呈现它的视图时,它的视图还没有呈现到视图层次结构上,这发生在我身上。我解决这个问题的方法是听NavigationControllerDelegate 的 didShow 方法。一旦调用了 didShow 方法,我就知道我可以呈现在我的 navigationController 上。

注意: usingdispatchQueueAsync.await(.now()) { //present }确实有效,但如果视图需要很长时间才能显示到视图层次结构上,它会很麻烦并且容易出现错误。

于 2021-12-03T01:01:55.340 回答