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

Posted

技术标签:

【中文标题】尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController【英文标题】:Attempt to present UIViewController on UIViewController whose view is not in the window hierarchy 【发布时间】:2012-08-08 10:47:41 【问题描述】:

刚开始使用 Xcode 4.5,我在控制台中收到此错误:

警告:尝试在 上显示 ,其视图不在窗口层次结构中!

视图仍在呈现,应用程序中的一切工作正常。这是 ios 6 中的新功能吗?

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

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

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

【问题讨论】:

我遇到了完全相同的问题,除了尝试在导航控制器上调用 presentViewController:animated:completion。您是否在应用委托中执行此操作? 不,我是从一个视图控制器到另一个视图控制器。你找到解决办法了吗? 在使用 Xcode 4.5 之前一直有效的部分代码存在同样的问题,我正在展示一个 UINavigationController,但这在之前一直有效。 我有同样的问题,没有解决。从应用程序委托中执行此操作,并且 rootviewcontroller 调用“presentViewController”作为 UITabBarController。 另外,如果在调用makeKeyAndVisible之前调用了这个方法,那么在之后移动它 【参考方案1】:

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

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


注意

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

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

【讨论】:

@James 你是对的,视图显然不在层次结构中,直到 after viewWillAppear 已解决并且一旦 viewDidAppear 已被调用。如果这是我的问题,我会接受这个答案;) @james 谢谢。使用 ViewDidAppear 也为我解决了这个问题。有道理。 我希望我能投票两次。我刚遇到这个问题,来到线程发现我上次看到这个时已经投票了。 请注意,当您在 viewDidAppear 中更改 VC 时,这会导致执行带有动画的 segue。导致背景闪烁/显示。 是的,诀窍是 viewWillAppear:(BOOL) 正确的动画。还有一件更重要的事情,你必须在方法中调用 super,如 [super viewDidAppear:animated];没有这个就不行。【参考方案2】:

另一个潜在原因:

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

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

【讨论】:

我遇到了同样的错误,你在这里的回答帮助我弄清楚了发生了什么,确实是这个错误,因为你而修复它,谢谢你,+1 我删除了旧的segue并将VC连接到VC。有没有办法将按钮连接到故事板到 VC,因为那样只会让我一直出错? 我遇到了同样的错误,您的回答解决了我的问题,感谢您的关注。亲切的问候。 大声笑,不小心我也创建了两个 vc:from button 和 performSegue,谢谢你的提示!!! 在我的情况下,我在循环中调用present(viewController, animated: true, completion: nil)【参考方案3】:

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

【讨论】:

仍然在其他场合也使用它们,所以我认为它们可能在视图的“生命周期”中被多次调用。 这也不是方法的用途——顾名思义。 viewDidAppear 是正确的。阅读视图生命周期是个好主意。 这是最好的解决方案。就我而言,在 viewDidAppear 中显示会导致在加载模式之前瞬间显示视图控制器,这是不可接受的。 在尝试显示警报时,这个答案对我来说效果最好。当我将它放入 viewDidLoad 和 viewWillAppear 时,警报不会显示。【参考方案4】:

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

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

【讨论】:

也为我工作【参考方案5】:

当我尝试在viewDidLoad 中显示UIViewController 时,我也遇到了这个问题。 James Bedford 的回答有效,但我的应用首先显示背景 1 或 2 秒。

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

- (void)viewDidLoad

    ...
    [self.view addSubview: navigationViewController.view];
    [self addChildViewController: navigationViewController];
    ...

【讨论】:

我认为你缺少 [navigationViewController didMoveToParentViewController:self] 我尝试了您的代码以及 foFox 的建议,当我将其从其父级中删除时,它不会消失。洛洛洛。仍然没有修复。 在 Swift3.1 中工作 @sunkehappy 上面两行要在presentviewcontroller之前使用,但是为什么会崩溃?【参考方案6】:

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

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

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

[self presentViewController:]

所以,我得到了一个 UIViewController:

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

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

【讨论】:

【参考方案7】:

TL;DR 您只能拥有 1 个 rootViewController 及其最近呈现的一个。所以不要尝试让一个 viewcontroller 呈现另一个 viewcontroller,因为它已经呈现了一个尚未被解除的。

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

如果您有一个想要呈现所有内容的 rootViewController,那么您可能会遇到这个问题。

这是我的 rootController 代码(open 是我从 root 呈现视图控制器的快捷方式)。

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。

【讨论】:

这似乎是一个更常见的问题,许多答案已经投票给你了。不幸的是,这非常有帮助 是的,一切都很好,但解决方案是什么......我有一个后台工作线程去服务器并左右显示故事板,整个方法都是假的......这很糟糕......应该是轻而易举的事情完全是个笑话,因为我想在后台线程中做的事情是:waitwaitdecidepush screen to front,这不可能是IOS????【参考方案8】:

Swift 5 - 后台线程

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

所以这个:

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

已修复:

DispatchQueue.main.async  [weak self] in
    self?.present(alert, animated: true, completion: nil)

【讨论】:

这是我的问题。谢谢!【参考方案9】:

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

【讨论】:

【参考方案10】:

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

【讨论】:

怎么样?你能详细说明吗?我面临同样的问题。这是我的代码让 initialViewControlleripad : UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "SplashController") as UIViewController self.window?.rootViewController = initialViewControlleripad self.window?.makeKeyAndVisible()【参考方案11】:

在我的情况下,我无法将我的放在类覆盖中。所以,这就是我得到的:

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)

【讨论】:

【参考方案12】:

你可以调用你的 segues 或在这个块内展示、推送代码:

override func viewDidLoad() 
    super.viewDidLoad()
    OperationQueue.main.addOperation 
        // push or present the page inside this block
    

【讨论】:

【参考方案13】:

我遇到了同样的问题。我必须嵌入一个导航控制器并通过它呈现控制器。下面是示例代码。

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


【讨论】:

【参考方案14】:

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

【讨论】:

我的意思是应该先停止/暂停视频。 实际上这对我有帮助,它把我带到了这个***.com/questions/20746413/…【参考方案15】:

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

【讨论】:

【参考方案16】:

效果很好,试试这个。Link

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

【讨论】:

【参考方案17】:

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

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

【讨论】:

【参考方案18】:

考虑到您想从几乎任何地方显示一些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。 如果有人决定将您的 sdk 嵌入具有扩展程序的应用程序中,您将崩溃并烧毁。将 UIViewController 传递给你的 sdk init 方法[s]。 你是真正的安东。此代码是在 Extensions 不存在且 SDK 尚未用于其中任何一个时编写的。我添加了一个保护子句来跳过这种极端情况。【参考方案19】:

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

【讨论】:

【参考方案20】:

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

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

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

这为我解决了问题。

【讨论】:

【参考方案21】:

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

【讨论】:

【参考方案22】:

必须写在下面一行。

self.searchController.definesPresentationContext = true

而不是

self.definesPresentationContext = true

在 UIViewController 中

【讨论】:

【参考方案23】:

使用 Swift 3...

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

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

【讨论】:

【参考方案24】:

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

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

【讨论】:

【参考方案25】:

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

【讨论】:

【参考方案26】:

在您的主窗口中,可能总是会出现与显示警报不兼容的过渡。为了允许在应用程序生命周期中的任何时间显示警报,您应该有一个单独的窗口来完成这项工作。

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

【讨论】:

【参考方案27】:

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

我找到了一个解决方案,方法是使用一个标志来指示调用了哪个 unwind 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
    

基本上,上面的代码可以让我从登录/注册 VC 中捕获展开并导航到仪表板,或者从忘记密码 VC 中捕获展开动作并导航到登录页面。

【讨论】:

【参考方案28】:

我通过将最顶层的 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

【讨论】:

【参考方案29】:

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

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

已通过将所有视图控制器上的演示模式从 自动 更改为 全屏来解决此问题。

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

【讨论】:

【参考方案30】:

斯威夫特 5

我在viewDidLayoutSubviews 中调用出现在viewDidAppear 中出现会导致视图控制器在加载模式之前瞬间显示,这看起来像一个丑陋的故障

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

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

【讨论】:

以上是关于尝试在其视图不在窗口层次结构中的 UIViewController 上呈现 UIViewController的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

警告:尝试在其视图不在窗口层次结构中的 ViewController 上呈现 ViewController (w/UIAlertController)

presentViewController over TabBarController 导致“尝试在其视图不在窗口层次结构中的 TabBarVC 上呈现 *VC”

Xamarin.Forms 警告:尝试使用 iOS 图像/手势识别器在其视图不在窗口层次结构中的 * 上呈现 *