有条件地从 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 故事板中的不同位置开始的主要内容,如果未能解决你的问题,请参考以下文章