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

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

警告:试图呈现<finishViewController: 0x1e56e0a0 > on <ViewController: 0x1ec3e000>的视图不在窗口层次结构中!

视图仍在显示,应用程序中的一切都在正常工作。这是iOS 6的新功能吗?

这是我用来在视图之间更改的代码:

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


[self presentViewController:finished animated:NO completion:NULL];
418818 次浏览

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

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


谨慎

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

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

当我试图在viewDidLoad中呈现UIViewController时,我也遇到了这个问题。James Bedford的答案是可行的,但我的应用程序先显示了1到2秒的背景。

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

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

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

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

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

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

[self presentViewController:]

我得到一个UIViewController

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

由于某种原因(逻辑错误),rootViewController与预期不同(正常的UIViewController)。然后我纠正了这个错误,用UINavigationController替换了rootViewController,问题就解决了。

我也有这个问题,但这和时间无关。我使用一个单例来处理场景,并将其设置为presenter。换句话说,“自我”没有连接到任何东西。我只是把它的内部“场景”变成了新的主持人,瞧,它成功了。(瞧,当你知道它的意思后,它就失去了它的触感,嘿)。

所以,这不是关于“神奇地找到正确的方法”,而是关于理解你的代码所处的位置以及它在做什么。我很高兴苹果给出了如此直白的警告信息,甚至还带有感情色彩。向苹果开发者致敬!!

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

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


}

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

另一个潜在原因:

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

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

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

你只能有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: {})
}

如果我在一行中调用open两次(不管时间流逝),这将在第一个开放上工作,但不是在第二个开放上。第二次打开尝试将导致上述错误。

然而,如果我关闭最近呈现的视图然后调用open,当我再次调用open(在另一个视图控制器上)时,它工作得很好。

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

我的结论是,只有MOST-RECENT-CALL的rootViewController在视图层次结构上(即使您没有解散它或删除视图)。我试着玩所有的加载器调用(viewDidLoad, viewDidAppear,并做延迟调度调用),我发现我能让它工作的唯一方法是从最顶层的视图控制器调用present。

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

当从嵌入在容器中的视图控制器执行segue时,你也可以得到这个警告。正确的解决方案是从容器的父容器使用segue,而不是从容器的视图控制器。

必须写在线下。

self.searchController.definesPresentationContext = true

而不是

self.definesPresentationContext = true

在ui

我最终得到了这样一个代码,最终对我(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
})
}
}

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

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

它工作得很好,试试这个

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

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

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

总结一下,不管你做什么确保模态没有被调用超过一次

用Swift 3…

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

我通过创建一个从ViewController到ViewController的segue来解决这个问题。

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

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

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

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

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

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

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

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

这为我解决了问题。

遗憾的是,这个公认的解决方案并不适用于我的案例。我试图在从另一个视图控制器unwind后导航到一个新的视图控制器。

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

基本上,上面的代码将让我从login/SignUp VC捕获unwind并导航到仪表板,或者从forget password VC捕获unwind操作并导航到登录页面。

这种警告可能意味着你试图通过Navigation Controller呈现新的View Controller,而这个Navigation Controller目前正在呈现另一个View Controller。要解决这个问题,你必须首先解散当前呈现的View Controller,并在完成后呈现新的。 警告的另一个原因可能是试图在main之外的线程上显示View Controller

在我的情况下,我不能把我的类重写。所以,这是我得到的:

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

我在斯威夫特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)
}
}

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

我修复了这个错误,将最顶部的视图控制器存储为常量,这是在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
}

我在更新Xcode后发现了这个bug,我相信是斯威夫特5。当我在展开视图控制器后通过编程方式直接启动segue时,问题就发生了。

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

# EYZ0。

你可以在界面构建器的属性面板中完成。或者参见这个答案了解如何以编程方式完成。

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

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

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

MainViewController呈现ViewControllerA,没有解散ViewControllerA,您尝试从MainViewController呈现viewControllerB(使用委托方法)。

在这个场景中,你必须确保你的viewcontrollerera被解散,然后ViewControllerB被调用。

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

你可以调用你的segue或present, push代码在这个块中:

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

斯威夫特5

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

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

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

}

Swift 5 -后台线程

如果警报控制器在后台线程上执行,那么&;Attempt to present…谁的视图不在窗口层次结构中?可能发生错误。

所以这个:

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

是这样固定的:

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

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

注意:使用dispatchQueueAsync.await(.now()) { //present }确实可以工作,但是如果视图需要很长时间才能显示到视图层次结构上,那么它很容易出现bug。

使用storyboards,我注意到我有一个按钮连接到两个@IBActions,这两个@IBActions函数分别呈现一个ViewController。在我移除一个连接后,问题就解决了。