繁体   English   中英

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

[英]Attempt to present UIViewController on UIViewController whose view is not in the window hierarchy

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

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

视图仍在呈现,应用程序中的所有内容都运行良好。 这是 iOS 6 中的新内容吗?

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

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

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

你从哪里调用这个方法? 我在尝试在viewDidLoad方法中显示模态视图控制器时遇到了问题。 我的解决方案是将此调用移动到viewDidAppear:方法。

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


警告

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

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

另一个潜在原因:

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

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

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

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

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

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

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

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

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

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

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

[self presentViewController:]

所以,我得到了一个 UIViewController:

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

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

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。

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

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

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

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

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

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

}

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

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

考虑到您想从几乎任何地方显示一些viewController ,我最终得到了这样一个最终对我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
        })
    }
}

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

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

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

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

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

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

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

Swift 5 - 后台线程

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

所以这:

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

已修复:

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

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

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

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

那为我修好了。

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

必须写在下面一行。

self.searchController.definesPresentationContext = true

代替

self.definesPresentationContext = true

在 UIViewController 中

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

使用 Swift 3...

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

我通过从 ViewController 到 ViewController 进行转场来解决这个问题。

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

它在 ViewWillAppear 中订阅,自从我们添加导航以转到另一个控制器,然后再回到它时,它被多次调用。

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

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

可悲的是,接受的解决方案对我的情况不起作用。 我试图在从另一个视图控制器放松后立即导航到一个新的视图控制器。

我通过使用标志来指示调用了哪个展开转场找到了解决方案。

@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 获取解压操作并导航到登录页面。

我通过将最顶层的视图控制器存储到常量中来修复此错误,该常量在 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 后出现的,我相信Swift 5 当我在展开视图控制器后直接以编程方式启动 segue 时,问题发生了。

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

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

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

斯威夫特 5

我在viewDidLayoutSubviews调用存在,因为在viewDidAppear呈现会导致在加载模式之前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(...)
    }
    
}

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

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

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

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

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

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

目前ViewControllerAMainViewController并没有解聘ViewControllerA尝试目前viewControllerBMainViewController (使用委托方法)。

在这种情况下,您必须确保您的 ViewControllerA 被解除,然后调用 ViewControllerB。

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

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

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

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

    let alert = UIAlertController(title: "", message: "YOU SUCCESSFULLY\nCREATED A NEW\nALERT CONTROLLER", preferredStyle: .alert)
    func okAlert(alert: UIAlertAction!)
    {
        
    }
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: okAlert))
    
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    var rootVC = window?.rootViewController
    
    if var topController = rootVC
    {
        while let presentedViewController = topController.presentedViewController
        {
            topController = presentedViewController
        }
        rootVC = topController
    }
    rootVC?.present(alert, animated: true, completion: nil)

它通常发生在您处理一些背景信息(如 Api 命中或特别是核心数据服务)后调用 Alert 时。 在应用程序的主线程中调用警报。 DispatchQueue.main.async { //call your alert in this main thread. Constants.alert(title: "Network Error,": message. "Please connect to inte.net and try again,": controller: self) }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM