有条件地从AppDelegate的情节提要中的不同位置开始


107

我有一个故事板设置了正常的登录名和主视图控制器,后者是成功登录后用户导航到的视图控制器。我的目标是,如果身份验证成功(存储在钥匙串中),则立即显示主视图控制器;如果身份验证失败,则显示登录视图控制器。基本上,我想在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中调用。如何使用现有的情节提要板来完成此任务?

编辑:

情节提要的初始视图控制器现在是未连接任何东西的导航控制器。我使用了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;
}

欢迎提出建议/改进!

Answers:


25

我假设您的情节提要板被设置为“主情节提要板”(UIMainStoryboardFileInfo.plist中的键)。在这种情况下,UIKit将加载情节提要,并将其初始视图控制器设置为窗口的根视图控制器,然后再发送application:didFinishLaunchingWithOptions:到AppDelegate。

我还假定情节提要中的初始视图控制器是导航控制器,您要将主视图或登录视图控制器推入该导航控制器。

您可以向窗口询问其根视图控制器,并将performSegueWithIdentifier:sender:消息发送给它:

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

1
我在我的application:didFinishLaunchingWithOptions:方法中实现了您的代码行。调试显示rootViewController确实是初始导航控制器,但是未执行segue(导航栏显示,其余为黑色)。我必须说,初始导航控制器不再具有rootViewController,而只有2个设置(StartLoginSegue和StartMainSegue)。
mmvie 2011年

3
是的,也不为我工作。如果对您不起作用,您为什么将其标记为已回答?
daidai 2012年

3
我相信这是正确的答案。您需要1.在您的应用程序委托上具有window属性,并且2. [[self window] makeKeyAndVisible]在尝试执行条件选择之前,请调用application:didFinishLaunchingWithOptions:。UIApplicationMain()应该发送消息makeKeyAndVisible,但仅在didFinish ... Options:完成后才这样做。有关详细信息,请在Apple文档中查找“协调视图控制器之间的工作”。
edelaney05 2012年

这是正确的想法,但效果不佳。请参阅我的答案以获取有效的解决方案。
马修·弗雷德里克

@MatthewFrederick如果初始控制器是导航控制器,则解决方案将起作用,但如果它是纯视图控制器,则解决方案将不会起作用。真正的答案是自己创建窗口和根视图控制器-实际上,这就是Apple在《视图控制器编程指南》中的建议。请参阅下面的详细信息。
followben 2012年

170

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

情节提要中实际上不需要虚拟导航控制器,无需在viewDidAppear:或任何其他黑客上隐藏视图并触发segues。

如果没有在plist文件中配置情节提要,则必须自己创建窗口和根视图控制器

- (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;
}

@AdamRabung Spot on-我刚刚复制了OP的变量名,但是为了清楚起见,我更新了答案。干杯。
followben 2013年

对于情节提要案例:如果您使用UINavigationViewcontroller作为根视图控制器,则需要推送下一个视图控制器。
Shirish Kumar,

对我来说,这是一种更直观的方法,而不是经过复杂的层次结构导航控制器。喜欢这个
Elliot Yap 2014年

您好@followben,在我的应用程序中,我的故事板中有rootViewController,它的tabBarController,并且所有与tabBar相关的VC也都在VC中设计,所以现在我有一个案例,我想展示我的应用程序演练,所以现在,当我的应用程序首次启动时,我想将演练VC而不是tabBarcontroller用作根VC,当演练完成时,我想将tabBarController用作rootViewController。我不明白该怎么做
Ranjit 2014年

1
如果对服务器的请求是异步的怎么办?
Lior Burg'Mar

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你是正确的。它们应该颠倒过来,我进行了编辑。
拉兹万

9

在您的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,可以在界面构建器中进行设置:

在此处输入图片说明

只需根据需要将其包装在逻辑中即可。

但是,如果您从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 2011年

2
您始终可以将第一个视图作为一个UIImage使用,该UIImage使用与启动画面相同的图像,并在后台检查是否已登录并显示下一个视图。
达伦(Darren)

3

Swift的实现方式相同:

如果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上有效的解决方案。为了加快初始加载速度并且不执行任何不必要的加载,我的情节提要文件中有一个完全空的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,它是Navigation Controller的Root View Controller。为此,只需按住控件,然后拖动Navigation Controller和MainViewController之间的连接,然后从提示中选择“ Relationship-Root View Controller”。

在MainViewController中:

- (void)viewDidLoad
{
    [super viewDidLoad];
    if (isLoggedIn) {
        [self performSegueWithIdentifier:@"HomeSegue" sender:nil];
    } else {
        [self performSegueWithIdentifier:@"LoginSegue" sender:nil];
    }
}

记住要在具有Home和Login视图控制器的MainViewController之间创建序列。希望这可以帮助。:)


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 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.