从AppDelegate开始有条件地在故事板中不同位置启动

108

我有一个设置好的故事板,其中包含工作登录和主视图控制器,后者是当登录成功时用户导航到的视图控制器。 我的目标是如果身份验证(存储在keychain中)成功,则立即显示主视图控制器,如果身份验证失败,则显示登录视图控制器。 基本上,我想在我的AppDelegate中实现这个:

// url request & response work fine, assume success is a BOOL here
// that indicates whether login was successful or not

if (success) {
          // 'push' main view controller
} else {
          // 'push' login view controller
}

我知道performSegueWithIdentifier:这个方法,但它是UIViewController的实例方法,所以无法从AppDelegate中调用。 如何在使用现有的storyboard的情况下完成这个操作?

编辑:

Storyboard的初始视图控制器现在是一个未连接到任何东西的导航控制器。我使用了setRootViewController:区别是因为MainIdentifier是UITabBarController。然后我的代码看起来像这样:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{        
    BOOL isLoggedIn = ...;    // got from server response

    NSString *segueId = isLoggedIn ? @"MainIdentifier" : @"LoginIdentifier";
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UIViewController *initViewController = [storyboard instantiateViewControllerWithIdentifier:segueId];

    if (isLoggedIn) {
        [self.window setRootViewController:initViewController];
    } else {
        [(UINavigationController *)self.window.rootViewController pushViewController:initViewController animated:NO];
    }

    return YES;
}

欢迎提出建议和改进!

10个回答

172

我对这里提出的一些解决方案感到惊讶。

实际上,你不需要在storyboard中使用虚拟导航控制器,也不需要在viewDidAppear:中隐藏视图和触发segues或任何其他黑科技。

如果你没有在plist文件中配置storyboard,你必须自己创建窗口和根视图控制器

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{        
    BOOL isLoggedIn = ...;    // from your server response

    NSString *storyboardId = isLoggedIn ? @"MainIdentifier" : @"LoginIdentifier";
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UIViewController *initViewController = [storyboard instantiateViewControllerWithIdentifier:storyboardId];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = initViewController;
    [self.window makeKeyAndVisible];

    return YES;
}

如果故事板在应用的plist中配置,那么当调用application:didFinishLaunching:时,窗口和根视图控制器将已经设置好,并且makeKeyAndVisible方法将自动在窗口上调用。

在这种情况下,操作甚至更加简单:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{        
    BOOL isLoggedIn = ...;    // from your server response

    NSString *storyboardId = isLoggedIn ? @"MainIdentifier" : @"LoginIdentifier";
    self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:storyboardId];

    return YES;
}

这对我来说更直观,而不是通过复杂的层次结构导航控制器。喜欢这个。 - Elliot Yap
你好@followben,我的应用程序中,我在storyBoard中有我的rootViewController,它是一个tabBarController,并且所有与tabBar相关联的VC也都是在VC中设计的,所以现在我有一个情况,我想展示我的应用程序的漫游,因此现在当我的应用程序首次启动时,我想将漫游VC作为根VC而不是tabBarController,当我的漫游完成时,我想将tabBarController作为rootViewController。我不明白如何做到这一点。 - Ranjit
еҫҲжЈ’зҡ„и§ЈеҶіж–№жЎҲгҖӮдҪҶжҳҜжҲ‘жӣҙе–ңж¬ўд»ҺrootVCи°ғз”ЁpresentViewController:жҲ–performSegueWithIdentifier:жқҘеҮҶеӨҮдёӨдёӘеҚ•зӢ¬зҡ„storyboardгҖӮ - Blaszard
对我来说,这显然是最好的答案,我想知道为什么它至少不能上升到第二名?! - lukas_o
1
如果向服务器发送的请求是异步的,该怎么办? - Lior Burg
显示剩余4条评论

25

我假设您的storyboard已经设置为"main storyboard"(在Info.plist文件中的key为UIMainStoryboardFile)。在这种情况下,UIKit将加载storyboard并将其初始视图控制器设置为您的窗口的根视图控制器,在发送application:didFinishLaunchingWithOptions:给您的AppDelegate之前。

我也假设您的故事板中的初始视图控制器是导航控制器,您想将其推入主视图控制器或登录视图控制器。

您可以向窗口请求其根视图控制器,然后向其发送performSegueWithIdentifier:sender:消息:

NSString *segueId = success ? @"pushMain" : @"pushLogin";
[self.window.rootViewController performSegueWithIdentifier:segueId sender:self];

1
我在我的application:didFinishLaunchingWithOptions:方法中实现了你的代码行。调试显示rootViewController确实是初始导航控制器,但是segue没有执行(导航栏显示,其余部分为黑色)。我必须说,初始导航控制器不再有rootViewController,只有两个segue(StartLoginSegue和StartMainSegue)。 - mmvie
3
是的,对我也不起作用。如果它对你没有用,为什么要标记它为已回答呢? - daihovey
3
我认为这是正确的答案。你需要在你的应用委托上拥有一个窗口属性,并且在 application:didFinishLaunchingWithOptions: 中调用 [[self window] makeKeyAndVisible],然后再尝试执行条件分支。UIApplicationMain() 应该会发送 makeKeyAndVisible 的消息,但只有在 didFinish...Options: 完成后才会这样做。查看苹果文档中的“协调视图控制器之间的工作”以获取详细信息。 - edelaney05
这是正确的想法,但并不完全可行。请查看我的答案以获取可行的解决方案。 - Matthew Frederick
@MatthewFrederick 如果初始控制器是导航控制器,您的解决方案将起作用,但如果它是普通视图控制器,则不会。真正的答案就是自己创建窗口和根视图控制器 - 实际上这就是苹果在“视图控制器编程指南”中推荐的方法。请参见下面的我的答案以获取详细信息。 - followben

18

如果你的故事板入口不是一个UINavigationController

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


    //Your View Controller Identifiers defined in Interface Builder
    NSString *firstViewControllerIdentifier  = @"LoginViewController";
    NSString *secondViewControllerIdentifier = @"MainMenuViewController";

    //check if the key exists and its value
    BOOL appHasLaunchedOnce = [[NSUserDefaults standardUserDefaults] boolForKey:@"appHasLaunchedOnce"];

    //if the key doesn't exist or its value is NO
    if (!appHasLaunchedOnce) {
        //set its value to YES
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"appHasLaunchedOnce"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }

    //check which view controller identifier should be used
    NSString *viewControllerIdentifier = appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;

    //IF THE STORYBOARD EXISTS IN YOUR INFO.PLIST FILE AND YOU USE A SINGLE STORYBOARD
    UIStoryboard *storyboard = self.window.rootViewController.storyboard;

    //IF THE STORYBOARD DOESN'T EXIST IN YOUR INFO.PLIST FILE OR IF YOU USE MULTIPLE STORYBOARDS
    //UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"YOUR_STORYBOARD_FILE_NAME" bundle:nil];

    //instantiate the view controller
    UIViewController *presentedViewController = [storyboard instantiateViewControllerWithIdentifier:viewControllerIdentifier];

    //IF YOU DON'T USE A NAVIGATION CONTROLLER:
    [self.window setRootViewController:presentedViewController];

    return YES;
}

如果您的故事板入口点是一个UINavigationController,请使用以下代码替换:

//IF YOU DON'T USE A NAVIGATION CONTROLLER:
[self.window setRootViewController:presentedViewController];

使用:

//IF YOU USE A NAVIGATION CONTROLLER AS THE ENTRY POINT IN YOUR STORYBOARD:
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
[navController pushViewController:presentedViewController animated:NO];

1
工作得很好。只是一个评论,这不是在他们最初进入后才显示“firstViewControllerIdentifier”吗?所以它不应该反过来吗?appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier; - ammianus
@ammianus 你说得对。它们应该被颠倒,我已经编辑了。 - Razvan

10

在你的AppDelegate的application:didFinishLaunchingWithOptions方法中,在return YES所在行之前添加:

UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
YourStartingViewController *yourStartingViewController = [[navigationController viewControllers] objectAtIndex:0];
[yourStartingViewController performSegueWithIdentifier:@"YourSegueIdentifier" sender:self];

YourStartingViewController替换为您实际的第一个视图控制器类的名称(您不一定想要出现的那个),将YourSegueIdentifier替换为该起始控制器和您想要实际启动的控制器之间的实际segue的名称(segue后面的那个)。

如果您不希望它总是发生,请将该代码放在if条件语句中。


6

假设您已经在使用Storyboard,那么您可以使用它来向用户展示MyViewController,这是一个自定义控制器(有点类似于followben的答案)。

AppDelegate.m中:

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    MyCustomViewController *controller = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:@"MyCustomViewController"];

    // now configure the controller with a model, etc.

    self.window.rootViewController = controller;

    return YES;
}

传递给instantiateViewControllerWithIdentifier的字符串是指故事板ID,可以在接口构建器中设置:

enter image description here

根据需要包装逻辑即可。

但是,如果您从UINavigationController开始,这种方法将不会给您提供导航控件。

要从通过接口构建器设置的导航控制器的起始点“向前跳转”,请使用此方法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UINavigationController *navigation = (UINavigationController *) self.window.rootViewController;

    [navigation.visibleViewController performSegueWithIdentifier:@"my-named-segue" sender:nil];

    return YES;
}

4
为什么不在首次显示登录屏幕时,在ViewDidLoad中检查用户是否已经登录,然后直接跳转至下一个屏幕?请考虑此方法。

2
这确实有效,但我的目标是在应用程序仍在等待服务器响应(无论登录是否成功)时显示启动图像。就像Facebook应用程序一样... - mmvie
2
你可以将第一个视图设置为一个UIImage,使用与启动画面相同的图片,并在后台检查登录状态并显示下一个视图。 - Darren

3

同样的实现方式-Swift版:

如果您在Storyboard中使用UINavigationController作为入口点

let storyboard = UIStoryboard(name: "Main", bundle: nil)

var rootViewController = self.window!.rootViewController as! UINavigationController;

    if(loginCondition == true){

         let profileController = storyboard.instantiateViewControllerWithIdentifier("ProfileController") as? ProfileController  
         rootViewController.pushViewController(profileController!, animated: true) 
    }
    else {

         let loginController =   storyboard.instantiateViewControllerWithIdentifier("LoginController") as? LoginController 
         rootViewController.pushViewController(loginController!, animated: true) 
    }

1
这是在iOS7上有效的解决方案。 为了加快初始加载速度并避免进行任何不必要的加载,我在我的Storyboard文件中有一个完全空白的UIViewcontroller称为“DUMMY”。然后我可以使用以下代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];

    NSString* controllerId = @"Publications";
    if (![NSUserDefaults.standardUserDefaults boolForKey:@"hasSeenIntroduction"])
    {
        controllerId = @"Introduction";
    }
    else if (![NSUserDefaults.standardUserDefaults boolForKey:@"hasDonePersonalizationOrLogin"])
    {
        controllerId = @"PersonalizeIntro";
    }

    if ([AppDelegate isLuc])
    {
        controllerId = @"LoginStart";
    }

    if ([AppDelegate isBart] || [AppDelegate isBartiPhone4])
    {
        controllerId = @"Publications";
    }

    UIViewController* controller = [storyboard instantiateViewControllerWithIdentifier:controllerId];
    self.window.rootViewController = controller;

    return YES;
}

0
我建议创建一个新的MainViewController作为导航控制器的根视图控制器。要做到这一点,只需按住Control键,然后拖动导航控制器和MainViewController之间的连接,从提示中选择“关系-根视图控制器”。
在MainViewController中:
- (void)viewDidLoad
{
    [super viewDidLoad];
    if (isLoggedIn) {
        [self performSegueWithIdentifier:@"HomeSegue" sender:nil];
    } else {
        [self performSegueWithIdentifier:@"LoginSegue" sender:nil];
    }
}

记得在MainViewController和Home、Login视图控制器之间创建segue。希望这可以帮到你。 :)


0

经过尝试多种不同的方法,我最终通过以下方式解决了这个问题:

-(void)viewWillAppear:(BOOL)animated {

    // Check if user is already logged in
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    if ([[prefs objectForKey:@"log"] intValue] == 1) {
        self.view.hidden = YES;
    }
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];

    // Check if user is already logged in
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    if ([[prefs objectForKey:@"log"] intValue] == 1) {
        [self performSegueWithIdentifier:@"homeSeg3" sender:self];
    }
}

-(void)viewDidUnload {
    self.view.hidden = NO;
}

如果你还没有进行得太深,Taylor,你可能想要重构成更简单的东西。请参阅我的答案以获取详细信息 :) - followben

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