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

Posted

技术标签:

【中文标题】有条件地从 AppDelegate 故事板中的不同位置开始【英文标题】:Conditionally start at different places in storyboard from AppDelegate 【发布时间】:2012-01-17 03:04:40 【问题描述】:

我有一个故事板设置了工作登录和主视图控制器,后者是登录成功时用户导航到的视图控制器。 我的目标是如果身份验证(存储在钥匙串中)成功则立即显示主视图控制器,如果身份验证失败则显示登录视图控制器。 基本上,我想在我的 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 的初始视图控制器现在是一个导航控制器,它没有连接到任何东西。我使用了 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;

欢迎提出建议/改进!

【问题讨论】:

【参考方案1】:

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

您的情节提要中确实不需要虚拟导航控制器,在 viewDidAppear: 上隐藏视图和触发 segues:或任何其他 hack。

如果您的 plist 文件中没有配置情节提要,you must create both the window and the root view controller yourself:

- (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 的变量名称,但为了清楚起见,我已经更新了我的答案。干杯。 对于故事板案例:如果您使用 UINavigationViewcontroller 作为根视图控制器,那么您需要推送下一个视图控制器。 这对我来说比通过复杂的层次导航控制器更直观。喜欢这个 你好@followben,在我的应用程序中,我在storyBoard中有我的rootViewController,它是一个tabBarController,所有与tabBar相关的VC也是在VC中设计的,所以现在我有一个案例,我想要显示我的应用程序的演练,所以现在当我的应用程序首次启动时,我想将演练 VC 作为根 VC 而不是 tabBarcontroller,当我的演练完成时,我想将 tabBarController 作为 rootViewController。怎么做我不明白 如果对服务器的请求是异步的呢?【参考方案2】:

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

我还假设情节提要中的初始视图控制器是导航控制器,您希望将主视图控制器或登录视图控制器推送到该控制器上。

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

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

【讨论】:

我在我的应用程序中实现了您的代码行:didFinishLaunchingWithOptions: 方法。调试显示 rootViewController 确实是初始导航控制器,但是没有执行 segue(显示导航栏,其余为黑色)。我必须说最初的导航控制器不再有 rootViewController,只有 2 个 segue(StartLoginSegue 和 StartMainSegue)。 是的,也不适合我。如果它对您不起作用,您为什么将其标记为已回答? 我相信这是正确的答案。您需要 1. 在您的应用程序委托上有一个窗口属性,并 2. 在 application:didFinishLaunchingWithOptions: 中调用 [[self window] makeKeyAndVisible],然后再尝试执行条件转场。 UIApplicationMain() 假设向 makeKeyAndVisible 发送消息,但仅在 didFinish...Options: 完成后才这样做。在 Apple 文档中查找“Coordinating Efforts between View Controllers”以获取详细信息。 这是正确的想法,但不太奏效。请参阅我的答案以获得有效的解决方案。 @MatthewFrederick 如果初始控制器是导航控制器,您的解决方案将有效,但如果它是普通视图控制器,则不会。真正的答案是自己创建窗口和根视图控制器——这确实是 Apple 在 View Controller Programming Guide 中推荐的。有关详细信息,请参阅下面的答案。【参考方案3】:

如果您的故事板的入口点不是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];

【讨论】:

运行良好。只是一个评论,这不是只有在他们最初输入后才显示“firstViewControllerIdentifier”吗?所以不应该反过来吗? appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier; @ammianus 你是对的。他们应该被颠倒,我编辑了。【参考方案4】:

在您的 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 之后的那个)。

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

【讨论】:

【参考方案5】:

鉴于您已经在使用 Storyboard,您可以使用它向用户展示 MyViewController,这是一个自定义控制器(稍微简化一下 followben's answer)。

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的字符串指的是Storyboard ID,可以在interface builder中设置:

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

但是,如果您从 UINavigationController 开始,则此方法不会为您提供导航控件。

要从通过界面生成器设置的导航控制器的起点“向前跳跃”,请使用以下方法:

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

    UINavigationController *navigation = (UINavigationController *) self.window.rootViewController;

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

    return YES;

【讨论】:

【参考方案6】:

为什么不先出现登录界面,检查用户是否已经登录并直接推送下一个界面?全部在 ViewDidLoad 中。

【讨论】:

这确实有效,但我的目标是只要应用程序仍在等待服务器响应(无论登录是否成功),就显示启动图像。就像 Facebook 应用程序一样...... 您总是可以让您的第一个视图只是一个 UIImage,它使用与您的启动画面相同的图像,并在后台检查是否已登录并显示下一个视图。【参考方案7】:

同样的 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) 
    

【讨论】:

【参考方案8】:

这是适用于 ios7 的解决方案。 为了加快初始加载并且不进行任何不必要的加载,我的 Storyboard 文件中有一个名为“DUMMY”的完全空的 UIViewcontroller。然后我可以使用以下代码:

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

【讨论】:

【参考方案9】:

我建议创建一个新的 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];
    

记得在 MainViewController 与 Home 和 Login 视图控制器之间创建 segues。 希望这可以帮助。 :)

【讨论】:

【参考方案10】:

在尝试了许多不同的方法后,我能够用这个来解决这个问题:

-(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,您可能想要重构一些更简单的东西。有关详细信息,请参阅我的答案:)

以上是关于有条件地从 AppDelegate 故事板中的不同位置开始的主要内容,如果未能解决你的问题,请参考以下文章

故事板中的当前选项卡

IOS开发。如何通过 segue 连接不同故事板中的两个场景

故事板中的层次结构以及在应用程序运行时调试时的层次结构不同

故事板中的 UIView 边界在代码中没有改变

iOS 故事板中的图像更改纵横比

故事板中的颜色与 UIColor 不匹配