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

时间:2012-07-28 02:26:52

标签: iphone ios storyboard appdelegate

我在没有明确答案的情况下以各种形式看过这个问题。我打算在这里问一下这个问题。我的应用程序需要在启动时工作... inits,一些网络调用,登录等。我不希望我的主视图控制器工作,直到完成。这样做的好方法是什么?

要求:

  • iOS5 +,故事板,ARC。
  • 在完成启动工作之前,主vc的视图无法显示。
  • 想要在启动工作完成后选择过渡样式到主vc。
  • 想在故事板中尽可能多地进行布局。
  • 代码应该干净地包含在显而易见的地方。

2 个答案:

答案 0 :(得分:13)

编辑,2017年7月 自从第一次写作以来,我已经将我的练习改为将我的启动任务交给他们自己的视图控制器。在那个VC中,我检查启动条件,在需要时显示“忙”UI等。根据启动时的状态,我设置窗口的根VC。

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

在您的主故事板中,添加一个名为LaunchViewController的新VC,并将其设为应用的初始vc。为您的应用程序的“真实”初始vc提供一个标识符,如“AppUI”(标识符位于IB的“标识”选项卡上)。

确定作为主要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并将其放在app storyboard中。
  2. 不要将任何segue拖到它上面,也不要让它成为盯着vc,只是让它漂浮在某个地方。
  3. 在故事板中的vc属性检查器中,将其标识符设置为“AppStartupViewController”。
  4. 在AppStartupViewController.m中,当满足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
        }
    }
    

    我希望这有用。

答案 1 :(得分:0)

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

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

  2. 将您的加载控制器添加到情节提要板,并使用命名的样式模式segue进行链接。

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

  4. - (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