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];
このメソッドはどこから呼び出していますか? viewDidLoad
name__メソッド内でモーダルView Controllerを表示しようとしているという問題がありました。私にとっての解決策は、この呼び出しをviewDidAppear:
メソッドに移動することでした。
ビューコントローラのビュー は、ロードされた時点ではウィンドウのビュー階層内では ではありませんが、viewDidLoad
name__メッセージが送信された時点では です。 ウィンドウ階層内で表示された後(viewDidAppear:
メッセージが送信されたとき)。
あぶない
presentViewController:animated:completion:
でviewDidAppear:
を呼び出すと、View Controllerのビューが表示されるたびにモーダルView Controllerが常に表示されているため(意味があります)、表示されているモーダルView Controllerが消えないという問題が発生する可能性があります...
これはモーダルView Controllerを表示するのに最適な場所ではないかもしれません。あるいは、表示中のView ControllerがモーダルView Controllerをすぐに表示するかどうかを決定できる追加の状態を保持する必要があるかもしれません。
もう一つの潜在的な原因:
誤って同じView Controllerを2回見せたときに、この問題が発生しました。 (ボタンが押されたときに呼び出されたperformSegueWithIdentifer:sender:
と、ボタンに直接接続されたセグエを使った2回目)。
事実上、2つのセグエが同時に発砲していて、私はエラーを受け取りました:Attempt to present X on Y whose view is not in the window hierarchy!
viewWillLayoutSubviews
とviewDidLayoutSubviews
(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
でView Controllerを表示しようとしたときにもこの問題に遭遇しました。 James Bedfordの答えを試してみました。それは動作しますが、私のアプリは1または2秒間最初に背景を表示します。
研究の後、私はついにこれを解決するためのもう一つの方法を見つけるのは子ビューコントローラを使うことです。
- (void)viewDidLoad
{
...
[self.view addSubview:navigationViewController.view];
[self addChildViewController:navigationViewController];
...
}
おそらく、私のように、あなたは間違ったルートを持っていますviewController
non-UIViewController
コンテキストでViewController
を表示したいのですが、
だから私はそのようなコードを使用することはできません:
[self presentViewController:]
だから、私はUIViewControllerを得る:
[[[[UIApplication sharedApplication] delegate] window] rootViewController]
何らかの理由で(論理的なバグ)、rootViewController
は予想外のものです(通常のUIViewController
)。それから私はrootViewController
をUINavigationController
に置き換えてバグを修正し、問題は解決しました。
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を2回続けて呼び出した場合(経過時間に関係なく)、これは最初のオープンではうまく機能しますが、2番目のオープンではうまくいきません。 2回目のオープンを試みると、上記のエラーが発生します。
ただし、最後に表示されたビューを閉じてからopenを呼び出した場合は、(別のビューコントローラで)openを呼び出したときに問題なく動作します。
func close(controller:UIViewController)
{
ROOT_VIEW_CONTROLLER.dismissViewControllerAnimated(true, completion: nil)
}
私が結論を出したのは、MOST-RECENT-CALLだけのrootViewControllerがビュー階層にあるということです(それを見逃したりビューを削除しなかったとしても)。すべてのローダー呼び出し(viewDidLoad、viewDidAppear、および遅延ディスパッチ呼び出し)で遊ぼうとしましたが、それを機能させる唯一の方法は、最上位のView Controllerから現在呼び出していることだけです。
私の問題は、ウィンドウ上でmakeKeyAndVisible()
を呼び出す前にUIApplicationDelegate
のdidFinishLaunchingWithOptions
メソッドでセグエを実行していたことです。
あなたが事実上どこからでもいくつかの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
})
}
}
再生したビデオのあるAVPlayerオブジェクトがある場合は、最初にビデオを一時停止する必要があります。
うまくいっていますこれを試してみてください。 リンク
UIViewController *top = [UIApplication sharedApplication].keyWindow.rootViewController;
[top presentViewController:secondView animated:YES completion: nil];
私は同じ問題を抱えていました。問題は、警告メッセージが消えたことをメインスレッドに通知するとすぐにperformSegueWithIdentifierが通知によってトリガーされたことです。
私の状況では、私はクラスをオーバーライドすることができませんでした。だから、ここで私が得たものです:
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
}
それがだれにでも役立つ場合、私の問題は非常にばかげていました。もちろん私のせいです。モーダルを呼び出しているメソッドが通知によってトリガーされていました。しかし、通知を正しく削除していなかったので、ある時点で、複数の通知を受け取ることになるため、モーダルは複数回呼び出されることになります。もちろん、モーダルを一度呼び出した後、それを呼び出すビューコントローラはビュー階層にはなく、この問題が発生するのはそのためです。あなたが期待するように、私の状況も他の問題の束を引き起こしました。
要約すると、あなたがしていることは何でも モーダルが2回以上呼ばれていないことを確認してください 。
ストーリーボードの セグエがなんらかの壊れた であることが私には思いました。セグエを削除すると(そしてまったく同じセグエを再度作成すると)問題は解決しました。
行の下に書いてください。
self.searchController.definesPresentationContext = true
の代わりに
self.definesPresentationContext = true
uIViewControllerで
コンテナに埋め込まれているView Controllerからセグエを実行するときにも、この警告を受け取ることがあります。正しい解決策は、コンテナのView Controllerからではなく、コンテナの親からの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"];
私は、start()
関数をdismiss
補完ブロック内に移動することで修正しました。
self.tabBarController.dismiss(animated: false) {
self.start()
}
Startには、UINavigationController用とUIImagePickerController
用の2つのself.present()
の呼び出しが含まれています。
それは私のためにそれを修正しました。
Swift 3を使って...
これに対するもう一つの考えられる原因は、私に起こりましたが、tableViewCellからストーリーボード上の別のViewControllerへのセグエを持っていたことです。セルがクリックされたときにもoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) {}
を使いました。
私はViewControllerからViewControllerへのセグエを作ることによってこの問題を修正しました。
悲しいことに、受け入れられた解決策は、私の場合のために動作しませんでした。私は別のビューコントローラからほどく右後に新しいビューコントローラに移動しようとしていました。
私は呼び出されたくつろぎセグエを示すフラグを使用して解決策を見つけました。
@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と、ログインページに移動しますからアンワインドのアクションをキャッチ。
私はこの問題を抱えていました、そして根本的な原因はボタンクリックハンドラ(TouchUpInside)を複数回購読していたことです。
それはViewWillAppearで購読していました、それは我々が別のコントローラに行くためにナビゲーションを追加して、そしてそれからそれに戻って戻るので、何度も呼ばれていました。
この種の警告は、View Controller
が現在別のNavigation Controller
を提示している間に、あなたが新しいNavigation Controller
からView Controller
を提示しようとしていることを意味します。それを修正するにはあなたは最初に現在表示されているView Controller
を却下し、完了時に新しいものを表示する必要があります。警告のもう1つの原因は、main
以外のスレッドでView Controller
を表示しようとしていることです。
私もこの問題を抱えていましたが、タイミングとは関係ありませんでした。私はシーンを処理するためにシングルトンを使用していました、そして私はプレゼンターとしてそれを設定しました。言い換えれば、 "Self"は何にもつながっていませんでした。私はちょうどその内側の「シーン」を新しいプレゼンターにし、出来上がり、それはうまくいった。 (あなたがその意味を学んだ後、Voilaはその接触を失います、ねえ)。
それで、それは「魔法のように正しい方法を見つけること」についてではなく、コードがどこにあるのか、そして何をしているのかを理解することです。 Appleがそのような平易な英語の警告メッセージを出したのは嬉しいです。それをしたAppleの開発者に賞賛を!
他の解決策が何らかの理由で見栄えがよくない場合でも、このように古き良き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:
でブール変数を台無しにする必要はありません