ビューがウィンドウ階層にない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

いいえ、ビューコントローラー間で行っています。何か解決策を見つけましたか?
カイルGoslan

Xcode 4.5を使用する前に常に機能していたコードの一部で同じ問題が発生しましたが、UINavigationControllerを提示していますが、これも以前は常に機能していました。
エマヌエーレフマガリ2012

私は同じ問題を抱えていますが、解決されていません。これをアプリデリゲートから実行し、rootviewcontrollerがUITabBarControllerである「presentViewController」を呼び出します。
darksider

3
また、makeKeyAndVisibleを呼び出す前にこのメソッドを呼び出す場合は、その後に移動します
mike_haney

回答:


1296

このメソッドはどこから呼び出しますか?viewDidLoadメソッド内でモーダルビューコントローラーを表示しようとしたときに問題が発生しました。私の解決策は、この呼び出しをviewDidAppear:メソッドに移動することでした。

私の推定は、View Controllerのビューがあることではない(それがロードされている時点で、ウィンドウのビュー階層にviewDidLoadメッセージが送信される)、それがあることが提示された後のウィンドウ階層に(ときviewDidAppear:メッセージが送信されます) 。


注意

あなたがへの呼び出し作るのですか場合presentViewController:animated:completion:viewDidAppear:モーダルビューコントローラは常に提示されていることにより、あなたが問題に遭遇することをいつでもView Controllerのビューが表示されます(理にかなっている!)とモーダルビューコントローラが離れて行くことは決してありません提示されているので... 。

これは、モーダルビューコントローラーを提示するのに最適な場所ではないかもしれません。または、いくつかの追加の状態を保持して、提示するビューコントローラーがモーダルビューコントローラーをすぐに提示するかどうかを決定できるようにする必要があるかもしれません。


6
@James正解です。viewWillAppearが解決されて、viewDidAppearが呼び出されるまでビューは階層にないようです。これが私の質問だった場合、私はこの回答を受け入れます;)
Matt Mc

6
@jamesありがとう。ViewDidAppearを使用すると、問題も解決しました。理にかなっています。
アリ

44
私はこれを2回賛成できるといいのですが。私はこの問題を抱えていて、最後にこれを見たときにすでに賛成していたことを知るためにスレッドに来ました。
Schrockwell

7
viewDidAppearでVCを変更すると、アニメーションでセグエが実行されることに注意してください。背景をフラッシュ/表示します。
Vincent

2
はい、トリックは正しいので、viewWillAppear:(BOOL)アニメーションです。[super viewDidAppear:animated]のように、メソッドでスーパーを呼び出す必要があるもう1つの重要なこと。これがないと機能しません。
BootMaker 2013年

66

別の考えられる原因:

同じビューコントローラを誤って2回表示していたときに、この問題が発生しました。(performSegueWithIdentifer:sender:ボタンが押されたときに一度呼び出され、ボタンに直接接続されたセグエでもう一度呼び出されます)。

事実上、2つのセグエが同時に発火し、エラーが発生しました。 Attempt to present X on Y whose view is not in the window hierarchy!


4
私は、同じエラーがあったが、ここにあなたの答えは先生あなたのためにそれを固定し、それは確かにこのエラーだった、私は何が起こっていたかを見つけ出す助け、ありがとう、1
samouray

1
古いセグエを削除し、VCをVCに接続しました。ボタンをストーリーボードに接続し、VCに接続する方法はありますか?
MCB 2015

私は同じエラーがありました、あなたの答えはあなたの注意のおかげで私の問題を解決しました。敬具。
iamburak 2016年

1
笑、偶然にも私は2つのvcを作成していました:fromボタンとperformSegue、ヒントありがとう!!!
Borzh 2017年

1
私の場合present(viewController, animated: true, completion: nil)、ループ内で呼び出していました。
サモ

38

viewWillLayoutSubviewsそして、viewDidLayoutSubviews(iOSの5.0以上)は、この目的のために使用することができます。これらは、viewDidAppearよりも前に呼び出されます。


3
それでも他の場合にも使用されるため、ビューの「ライフタイム」で何度も呼び出される可能性があると思います。
ジョニー2013年

名前が示すように、それはメソッドの目的でもありません。viewDidAppearは正しいです。ビューのライフサイクルについて詳しく読むことをお勧めします。
tooluser、2013

これが最良の解決策です。私の場合、viewDidAppearで表示すると、モーダルがロードされる前にビューコントローラが一瞬表示されますが、これは許容できません。
TMilligan 2013年

この回答は、アラートを表示するときに最も効果的でした。アラートを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];

私のためにも働いた
Aziz Javed 2017年

17

でを表示しようとしたときにも、この問題が発生しUIViewControllerました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

Swift3.1で動作
Kang Byul

presentviewcontrollerの前に使用される2行以上の@sunkehappyが、なぜクラッシュしたのですか?
Iyyappan Ravi 2017

14

おそらく、私のように、あなたは間違った根を持っています viewController

私は表示するViewControllerにはnon-UIViewController、コンテキスト

だから私はそのようなコードを使うことはできません:

[self presentViewController:]

だから、私はUIViewControllerを取得します:

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

なんらかの理由で(論理的なバグ)、これrootViewControllerは予期されたものではありません(正常なUIViewController)次に、バグを修正して、に置き換えれrootViewControllerUINavigationController、問題は解決しました。


8

TL; DR rootViewControllerは1つと、最後に表示されたもののみが可能です。そのため、却下されていないビューコントローラーが既に表示されている場合は、ビューコントローラーに別のビューコントローラーを表示させないでください。

私自身のテストのいくつかを行った後、私は結論に達しました。

すべてを表示したい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: {})
}

(経過時間に関係なく)2回続けてopenを呼び出すと、最初のオープンでは問題なく機能しますが、2番目のオープンでは機能しません。2回目の開こうとすると、上記のエラーが発生します。

ただし、最後に表示されたビューを閉じてからopenを呼び出すと、(別のビューコントローラーで)openを再度呼び出すと問題なく機能します。

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

私が結論付けたのは、MOST-RECENT-CALLのみのrootViewControllerがビュー階層にあるということです(たとえそれを閉じたり、ビューを削除しなかったとしても)。すべてのローダー呼び出し(viewDidLoad、viewDidAppear、および遅延ディスパッチ呼び出し)を試してみましたが、これを機能させる唯一の方法は、最上部のビューコントローラーから存在するものを呼び出すことだけであることがわかりました。


これははるかに一般的な問題のように思われますが、多くの回答があなたの票を投じました。残念ながら、これは非常に役に立ちました
rayepps

はい、すべて順調ですが、解決策は何ですか...私はバックグラウンドワーカースレッドをサーバーに移動して、ストーリーボードを左右中央に表示し、方法論全体が偽物です。それはひどいです。私は彼のバックグラウンドスレッドでやりたいので冗談です:wait wait decide push screen to frontそして、それはIOSは不可能です????
Heelis氏、

5

私の問題は、ウィンドウを呼び出す前にUIApplicationDelegatedidFinishLaunchingWithOptionsメソッドでセグエを実行していたことでしたmakeKeyAndVisible()


どうやって?詳しく説明できますか?私は同じ問題に直面しています。これは私のコードlet initialViewControlleripad:UIViewController = mainStoryboardIpad.instantiateViewController(withIdentifier: "SplashController")as 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

それが誰かを助ける場合、私の問題は非常にばかげていました。もちろん、私のせいです。通知により、モーダルを呼び出すメソッドがトリガーされました。しかし、私は通知を正しく削除していなかったため、ある時点で、複数の通知が表示され、モーダルが複数回呼び出されていました。もちろん、モーダルを1回呼び出すと、モーダルを呼び出すビューコントローラーはビュー階層に存在しなくなるため、この問題が発生します。予想通り、私の状況は他にもたくさんの問題を引き起こしました。

要約すると、何をしている場合でも、モーダルが2回以上呼び出されていないことを確認してください。


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を表示する、それ以外はUIのない​​SDKライブラリです。
igraczech

誰かが拡張機能を持つアプリにあなたのSDKを埋め込むことを決定した場合、クラッシュして書き込みます。UIViewControllerをsdk initメソッドに悪用するために渡します。
Anton Tropashko 16

あなたは本当のアントンです。このコードは、拡張機能が存在せず、SDKがまだいずれも使用されていないときに作成されました。このエッジケースをスキップするために、guard句を追加しました。
igraczech 16

3

この種類の警告は、現在別のを提示しView ControllerているNavigation Controllerときに、あなたが新しいものをNavigation Controller提示しようとしていることを意味しますView Controller。それを修正するには、現在提示さView Controllerれているものを最初に却下し、完了時に新しいものを提示する必要があります。警告のもう1つの原因は、View Controller以外のスレッドに表示しようとすることですmain


3

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

2

start()関数をdismiss完了ブロック内に移動することで修正しました。

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

Startにはself.present()、UINavigationControllerに対する2つの呼び出しと、に対するもう1 つの呼び出しが含まれていUIImagePickerControllerます。

それで解決しました。


1

コンテナーに埋め込まれているビューコントローラーからセグエを実行すると、この警告が表示されることもあります。正しい解決策は、コンテナーのビューコントローラーからではなく、コンテナーの親からセグエを使用することです。


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

偶然、ストーリーボードセグエが壊れていようです。セグエを削除して(そしてまったく同じセグエをもう一度作成して)、問題を解決しました。


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
}

次に、必要なVCを提示します 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

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の更新後に到着したスウィフト5。この問題は、ビューコントローラーを巻き戻した直後にプログラムでセグエを起動したときに発生していました。

関連するバグを修正している間に解決策が到着しました。つまり、ユーザーはページを下にスワイプすることでセグエを巻き戻すことができました。これは私のプログラムの論理を壊しました。

すべてのView Controllerのプレゼンテーションモードを自動から全画面に変更することで修正されました

これは、インターフェースビルダーの属性パネルで実行できます。または、プログラムでそれを行う方法については、この回答を参照してください。


0

私もこの問題を抱えていましたが、タイミングとは関係ありませんでした。シングルトンを使用してシーンを処理し、それをプレゼンターとして設定しました。つまり、「Self」は何にも接続されていませんでした。私はその内側の「シーン」を新しいプレゼンターと出来上がりにしただけで、うまくいきました。(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
これは今は機能するかもしれませんが、Appleが動作を変更して将来これを壊すという保証はありません。次に、コードを確認してもう一度修正してみると、なぜこの一見不要なディスパッチを実行していたのか不思議に思うでしょう。
Cruinh

0時間でのディスパッチにより、すでにかなりの時間を節約できました。論理的に機能するはずのものが、通常、それなしでは機能しない場合があります。ですから、なぜあなたが明白ではないことをするのか(そのような派遣だけではないのか)について自分や他の人にコメントしてください。
ダニーP

2
あなたは問題をハッキングしていて、それを解決していないだけです。
マイケルピーターソン

0

これは、ナビゲーションコントローラが利用可能な場合、ビューコントローラを表示するために機能します。 self.navigationController?.present(MyViewController、animated:true、completion:nil)また、アラートやメールコントローラを表示することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.