刚开始使用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方法中呈现一个模态视图控制器。我的解决方案是将这个调用移动到viewDidAppear:方法。
我的假设是,视图控制器的视图在它被加载时(当viewDidLoad消息被发送时)不在窗口的视图层次结构中,但在它被呈现后(当viewDidAppear:消息被发送时)它在窗口层次结构中。
谨慎
如果你确实调用了viewDidAppear:中的presentViewController:animated:completion:,你可能会遇到这样一个问题,即每当视图控制器的视图出现时,模态视图控制器总是被呈现(这是有意义的!),因此模态视图控制器被呈现永远不会消失……
也许这不是呈现模态视图控制器的最佳位置,或者可能需要保留一些额外的状态让呈现的视图控制器决定是否应该立即呈现模态视图控制器。
可能和我一样,你有一个错误的根viewController
我想在非uiviewcontroller上下文中显示一个ViewController,
所以我不能使用这样的代码:
[self presentViewController:]
我得到一个UIViewController
[[[[UIApplication sharedApplication] delegate] window] rootViewController]
由于某种原因(逻辑错误),rootViewController是其他的东西比预期的(一个正常的UIViewController)。然后我纠正了这个错误,用UINavigationController替换了rootViewController,问题就解决了。
我也有同样的问题。我必须嵌入一个导航控制器,并通过它呈现控制器。下面是示例代码。
- (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
}
如果其他解决方案因为某些原因看起来不太好,你仍然可以使用这个不错的老方法,用延迟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中混乱布尔变量:
你只能有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。
我已经结束了这样一个代码,最终工作给我(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
})
}
}
In case it helps anyone, my issue was extremely silly. Totally my fault of course. A notification was triggering a method that was calling the modal. But I wasn't removing the notification correctly, so at some point, I would have more than one notification, so the modal would get called multiple times. Of course, after you call the modal once, the viewcontroller that calls it it's not longer in the view hierarchy, that's why we see this issue. My situation caused a bunch of other issue too, as you would expect.
总之,无论你做什么,都要确保模态不会被调用超过一次。
在主窗口中,可能总是会出现与显示警报不兼容的过渡。为了允许在应用程序生命周期的任何时候显示警报,您应该有一个单独的窗口来完成这项工作。
/// 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"];
遗憾的是,这个公认的解决方案并不适用于我的案例。我试图在从另一个视图控制器unwind后导航到一个新的视图控制器。
我找到了一个解决方案,使用一个标志来指示哪个unwind segue被调用。
@IBAction func unwindFromAuthenticationWithSegue(segue: UIStoryboardSegue) {
self.shouldSegueToMainTabBar = true
}
@IBAction func unwindFromForgetPasswordWithSegue(segue: UIStoryboardSegue) {
self.shouldSegueToLogin = true
}
然后用present(_ viewcontrollertoppresent: 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操作并导航到登录页面。
在我的情况下,我不能把我的类重写。所以,这是我得到的:
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)
}
该消息显示为警告,有时代码拒绝工作。(!引文:更新的SDK可能有严格的规则)。
我遇到这种情况的原因不止一个,主要是复杂的视图控制器场景。举个例子。
Scenario: MainViewController (responsible to load: ViewControllerA & ViewControllerB)
从MainViewController中呈现ViewControllerA,并且在不解散ViewControllerA的情况下,尝试从MainViewController中呈现viewControllerB(使用委托方法)。
在这个场景中,你必须确保你的viewcontrollerera被解散,然后ViewControllerB被调用。
因为在呈现ViewControllerA之后(ViewControllerA开始负责显示视图和视图控制器,当MainViewController尝试加载另一个视图控制器时,它拒绝抛出警告)。
Swift 5 -后台线程
如果一个警报控制器在后台线程上执行,那么“Attempt to present…”“其视图不在窗口层次结构中”的错误可能会发生。
所以这个:
present(alert, animated: true, completion: nil)
是这样固定的:
DispatchQueue.main.async { [weak self] in
self?.present(alert, animated: true, completion: nil)
}
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)