AppDelegate,rootViewController和presentViewController AppDelegate是iOS应用程序的主要入口点,它负责管理应用程序的生命周期并处理系统级事件。rootViewController是应用程序视图层次结构中的顶级视图控制器,它负责管理其他视图控制器。presentViewController是一种用于在当前视图控制器上显示模态视图控制器的方法。

41

我正在进行Facebook集成教程,如果用户有当前状态的有效令牌,我想显示我的MainViewViewController,否则我想显示LoginViewController。

MainViewAppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
        // To-do, show logged in view
    } else {
        // No, display the login page.
        [self showLoginView];
    }
    return YES;
}
- (void)showLoginView
{
    UIStoryboard *mainstoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard"          bundle:nil];
    LoginViewController* loginViewController = [mainstoryboard      instantiateViewControllerWithIdentifier:@"LoginViewController"];
    [self.window.rootViewController presentViewController:loginViewController animated:YES completion:NULL];
}

控制台错误:

Warning: Attempt to present <LoginViewController: 0xb492fd0> on <MainViewViewController: 0xb1bd820> whose view is not in the window hierarchy!

我不想使用导航控制器。

6个回答

137

我遇到了同样的问题。基于这个问题的答案,我在presentViewController:animated:completion:之前添加了[self.window makeKeyAndVisible],这样就解决了我的问题。

在你的情况下,showLoginView变为

- (void)showLoginView
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
    LoginViewController *loginViewController = [storyboard instantiateViewControllerWithIdentifier:@"LoginViewController"];
    [self.window makeKeyAndVisible];
    [self.window.rootViewController presentViewController:loginViewController animated:YES completion:NULL];
}

2
它有效!谢谢!很抱歉,我没有足够的声望来投票支持你的答案...希望其他人能做到;) - Boris-V
1
谢谢!我差点就放弃了! - Michael D. Irizarry
2
谢谢,伙计!!我花了几个小时才找到这个答案。 - Artem Z.

38
有时从window.rootViewController呈现模态视图控制器可能会产生相同的警告并且没有效果。 例如这样的视图控制器层次结构:
- [MYUITableViewController](由MYUIViewController模态呈现) - [MYUIViewController](下面是UINavigationController的rootViewController) - [UINavigationController](根)
现在调用
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:[UIViewController new] animated:YES completion:nil];

会引起这个确切的警告(在iOS6和7 Sim上进行了测试)

解决方案: 不要使用rootViewController,而是使用由它呈现的顶部视图控制器:

    UIViewController *topRootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topRootViewController.presentedViewController) 
    {
        topRootViewController = topRootViewController.presentedViewController;
    }

    [topRootViewController presentViewController:yourController animated:YES completion:nil];
  • 有时候keyWindow可能被空的rootViewController替换了(在iPhone上显示UIAlertViews、UIActionSheets等),这种情况下你应该使用UIView的window属性。

我收到了“Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x17564c30>”的错误信息。 - Slavcho
请大家帮忙,我有同样的问题,但是不知道应该使用哪个 Swift 版本?我使用以下代码: var sharedApplicationVC : UIViewController! = self.window?.rootViewController while var vvc = sharedApplicationVC.presentedViewController{ sharedApplicationVC = vvc } sharedApplicationVC.presentViewController(closeableVC, animated: true, completion: nil) ,但是出现了内存泄漏问题:<UIKit/UIView.h> 可能也会有所帮助。 致命错误:在解包可选值时意外发现 nil。 - Renat Gatin
1
如果屏幕上有UIAlertController显示,并且想要呈现另一个ViewController,这是否有效? - user4790024
谢谢! :) 救了我的一天 :) - Adam Studenic
1
是的!我尝试了所有的方法,但都没用。这个对我来说是正确答案。挽救了我的一天!谢谢。 - TheAlphaGhost

4

Stepan Generalov的回答Swift 3中对我非常有用!当然,使用了新的语法等等,所以我会在这里将其复制:

let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "MainApp") as! ViewController

var topRootViewController: UIViewController = (UIApplication.shared.keyWindow?.rootViewController)!
while((topRootViewController.presentedViewController) != nil){
    topRootViewController = topRootViewController.presentedViewController!
}
topRootViewController.present(vc, animated: true, completion: nil)

在这种情况下,“MainApp”是我的主视图控制器的标识符。



我知道还有其他方法,但如果您需要为打开应用程序的不同部分使用不同的URL方案,则必须在AppDelegate中处理它,因此这很完美。

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {}

使用这种方法,您只需将url作为字符串进行检查,然后决定是执行上面编写的代码还是类似于另一个视图控制器的不同标识符的代码(withIdentifier)。


为什么要使用while而不是简单的if?if对我来说更好。 - zgluis

1
如果您不使用故事板,首先需要创建YourViewController。前往文件 -> 新建 -> 文件...(或快捷键 - command + N) enter image description here 之后,选择Cocoa Touch Class。点击下一步,或按Enter enter image description here 然后为您的视图控制器输入名称并单击下一步 enter image description here
#import "AppDelegate.h"
#import "YourViewController.h"

@interface AppDelegate ()

@end

@implementaion AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// Init window
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
[self.window makeKeyAndVisible];

// Init YourViewController
YourViewController *viewController = [[YourViewController alloc] init];

// Init YourNavigationController
UINavigationController *navigationContoller = [[UINavigationController alloc] initWithRootViewController: viewController];

// Set rootViewController
self.window.rootViewController = navigationContoller;

return YES;

}

@end

Swift 3/4 示例

import UIKit

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

//1-st step
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()

//2-nd - create a window root controller, and create a layout
let layout = UICollectionViewFlowLayout()
window?.rootViewController = UINavigationController(rootViewController: HomeController(collectionViewLayout: layout))

return true

}


1
在Swift 3中:-
let storyboard = UIStoryboard(name: "Login", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
window?.makeKeyAndVisible()
window?.rootViewController?.present(viewController, animated: true, completion: nil)

1
您可以使用以下Objective-C代码从根视图控制器启动新的viewController:
UIViewController *presenter = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;

[presenter presentViewController:yourViewController animated:YES completion:nil];

Don't forget to add this:

#import "AppDelegate.h"

除非rootViewController在窗口层次结构中,否则这将无法工作,而这并不总是情况。当我尝试这样做时,我的应用程序收到了一个警告。 - Brainware
1
@Brainware 你可以在AppDelegate的didFinishLaunchingWithOptions函数中设置根视图控制器,例如:UIViewController *mainVC = [[MainVC alloc] initWithNibName:@"MainXB" bundle:nil]; [self setRootViewController:mainVC];或者你可以定义一个引用对象来引用你的根视图控制器,并在不同的场景下改变它的值。 - Sabrina
另外,检查一下 @Stepan-Generalov 的解决方案。我认为它完全没问题。 - Sabrina
1
谢谢。它能够很好地从AppDelegate显示另一个控制器。我已经将其集成到React Native应用程序中,使用本机模块。 - PEHLAJ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接