如何使用故事板呈现飞溅/登录视图控制器

问题描述:

我以各种形式看到此问题,但没有明确的答案。我会在这里提问并回答。我的应用程序需要在启动时进行工作... inits,几个网络调用,登录等。我不想让我的主视图控制器工作,直到完成。这样做有什么好的模式?如何使用故事板呈现飞溅/登录视图控制器

要求:

  • 的iOS5 +,情节串连图板,ARC。
  • 直到启动工作完成,主vc的视图才能出现。
  • 想要在启动工作完成时向主vc选择转换样式。
  • 想要在故事板中尽可能多地进行布局。代码应该干净地包含在某个明显的地方。

编辑,2017年7月 由于第一次写,我已经改变了我的做法是一个,我给启动任务自己的视图控制器。在那个VC中,我检查启动条件,根据需要提供一个“繁忙”的UI等。根据启动时的状态,我设置窗口的根VC。

除了解决OP问题之外,此方法还具有更好地控制启动UI和UI转换的其他好处。下面是如何做到这一点:

在您的主要故事板中,添加一个名为LaunchViewController的新VC,并将其作为应用程序的初始vc。为您的应用程序的“真实”初始vc指定一个标识符,如“AppUI”(标识符位于IB的Identity标签中)。

确定其他主要UI流(例如注册/登录,教程等)启动的vcs并提供这些描述性标识符。 (有些人喜欢将每个流程都保存在自己的故事板中,这是一个很好的做法,IMO)。

另一个不错的可选想法:给你的应用程序的启动故事板的vc标识符(如“LaunchVC”),以便你可以抓住它并在启动过程中使用它的视图。这将在启动期间以及在您执行启动任务时为用户提供无缝体验。

这里是我的LaunchViewController样子....

@implementation LaunchViewController 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    // optional, but I really like this: 
    // cover my view with my launch screen's view for a seamless start 
    UIStoryboard *storyboard = [self.class storyboardWithKey:@"UILaunchStoryboardName"]; 
    UINavigationController *vc = [storyboard instantiateViewControllerWithIdentifier:@"LaunchVC"]; 
    [self.view addSubview:vc.view]; 
} 

- (void)viewWillDisappear:(BOOL)animated { 
    [super viewWillDisappear:animated]; 
    [self hideBusyUI]; 
} 

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

    // start your startup logic here: 
    // let's say you need to do a network transaction... 
    // 
    [someNetworkCallingObject doSomeNetworkCallCompletion:^(id result, NSError *error) { 
     if (/* some condition */) [self.class presentUI:@"AppUI"]; 
     else if (/* some condition */) [self.class presentUI:@"LoginUI"]; 
     // etc. 
    }]; 
} 

#pragma mark - Busy UI 

// optional, but maybe you want a spinner or something while getting started 
- (void)showBusyUI { 
    // in one app, I add a spinner on my launch storyboard vc 
    // give it a tag, and give the logo image a tag, too 
    // here in animation, I fade out the logo and fade in a spinner 
    UIImageView *logo = (UIImageView *)[self.view viewWithTag:32]; 
    UIActivityIndicatorView *aiv = (UIActivityIndicatorView *)[self.view viewWithTag:33]; 
    [UIView animateWithDuration:0.5 animations:^{ 
     logo.alpha = 0.0; 
     aiv.alpha = 1.0; 
    }]; 
} 

- (void)hideBusyUI { 
    // an animation that reverses the showBusyUI 
} 

#pragma mark - Present UI 

+ (void)presentUI:(NSString *)identifier { 
    UIStoryboard *storyboard = [self storyboardWithKey:@"UIMainStoryboardFile"]; 
    UINavigationController *vc = [storyboard instantiateViewControllerWithIdentifier:identifier]; 

    UIWindow *window = [UIApplication sharedApplication].delegate.window; 
    window.rootViewController = vc; 

    // another bonus of this approach: any VC transition you like to 
    // any of the app's main flows 
    [UIView transitionWithView:window 
         duration:0.3 
         options:UIViewAnimationOptionTransitionCrossDissolve 
        animations:nil 
        completion:nil]; 
} 

+ (UIStoryboard *)storyboardWithKey:(NSString *)key { 
    NSBundle *bundle = [NSBundle mainBundle]; 
    NSString *storyboardName = [bundle objectForInfoDictionaryKey:key]; 
    return [UIStoryboard storyboardWithName:storyboardName bundle:bundle]; 
} 

@end 

下原来的答案,但我更喜欢我目前的做法

让我们表达应用程序的准备运行具有主VC布尔,是这样的:

BOOL readyToRun = startupWorkIsDone && userIsLoggedIn; 
  1. 创建一个AppStartupViewController并将它放在应用程序故事板中。
  2. 不要拖动任何segue到它,并且不要将它变成凝视vc,只是让它在某处浮动。
  3. 在故事板中的vc属性检查器中,将其标识符设置为“AppStartupViewController”。

在AppStartupViewController中。男,当readyToRun条件已经满足,就可以辞退自己:

self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; // your choice here from UIModalTransitionStyle 
[self dismissViewControllerAnimated:YES completion:nil]; 

现在,每当应用程序被激活,它可以检查准备工作,因此,它在需要出示AppStartupViewController。 在AppDelegate.h

- (void)applicationDidBecomeActive:(UIApplication *)application { 

    BOOL readyToRun = startupWorkIsDone && userIsLoggedIn; 

    if (!readyToRun) { 
     UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]; 
     AppStartupViewController *startupVC = [storyboard instantiateViewControllerWithIdentifier:@"AppStartupViewController"]; 

     [self.window.rootViewController presentViewController:startupVC animated:NO completion:nil]; 
     // animate = NO because we don't want to see the mainVC's view 
    } 
} 

这主要是答案,但有一个顺利。不幸的是,在AppStartupViewController呈现之前,主要的vc被加载(没关系),并得到一个viewWillAppear:消息(不好)。这意味着我们必须蔓延一点点额外的启动逻辑,这样,在MainViewController.m:

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

    if (readyToRun) { 
     // the view will appear stuff i would have done unconditionally before 
    } 
} 

我希望这是有帮助的。

+0

,我尝试了你的解决方案为我的应用程序,但我确实得到了你提到的顺利。 您是否介意说明“视图会出现我以前无条件完成的任务” – 2012-08-01 14:47:24

+0

当然 - 在我提供的解决方案中,您的应用中的主要vc将获取viewWillAppear的两个调用: - 首先在您的AppStartup vc被呈现,而第二个主vc真的要出现了。问题在于第一个。我希望我的主要vc能够假设在获取任何查看消息之前,所有事情都已准备好,但首先(并且错误由Apple,我认为)发送给viewWillAppear:发生在我运行启动逻辑之前。 (它不会出现,为什么它会得到这个信息?)继续... – danh 2012-08-01 16:13:00

+0

因此,我在那里添加了readyToRun条件。 “视图将显示内容......”是您打算在该方法中执行的任何操作,任何应该只在应用程序准备就绪时才运行的内容。这很难表达。请考虑如何编辑我的soln以获得更多清晰度。 – danh 2012-08-01 16:14:43

使用导航控制器时的另一种解决方案。

  1. 您的导航控制器是初始视图控制器,将您的主控制器设置为导航控制器根视图。

  2. 将您的加载控制器添加到故事板,并链接到样式模态的命名segue。

  3. 在您的主控制器的viewWillAppear触发segue(每个应用程序只运行一次)。

- (void)viewWillAppear:(BOOL)animated 
{ 
    [super viewWillAppear:animated]; 
    if(_isFirstRun) 
    { 
     _isFirstRun = NO; 
     [self performSegueWithIdentifier:@"segueLoading" sender:nil]; 
    } 
} 

如果触发viewDidLoad中的SEGUE它不会工作可能是因为导航控制器的动画还没有完成,如果你不介意我问,你会收到一个unbalanced calls to begin/end appearance transitions