且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-12-30 11:28:58

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

EDIT, July 2017 Since the first writing, I've changed my practice to one where I give the start up tasks to their own view controller. In that VC, I check startup conditions, present a "busy" UI if needed, etc. Based on the state at startup, I set the window's root VC.

除了解决 OP 问题之外,这种方法还有其他好处,可以更好地控制启动 UI 和 UI 转换.操作方法如下:

In addition to solving the OP problem, this approach has the additional benefits of giving better control of the startup UI and UI transitions. Here's how to do it:

在您的主故事板中,添加一个名为 LaunchViewController 的新 VC,并使其成为应用程序的初始 VC.为您的应用的真实"初始 vc 提供一个标识符,例如AppUI"(标识符位于 IB 的身份"选项卡上).

In your main storyboard, add a new VC, called LaunchViewController and make it the app's initial vc. Give your app's "real" initial vc an identifier like "AppUI" (identifiers are on the Identity tab in IB).

识别作为主要 UI 流程开始的其他 vc(例如注册/登录、教程等)并给出这些描述性标识符.(有些人更喜欢将每个流程保留在自己的故事板中.这是一个很好的做法,IMO).

Identify other vcs that are the starts of main UI flows (e.g. Signup/Login, Tutorial, and so on) and give these descriptive identifiers, too. (Some prefer to keep each flow in it's own storyboard. That's a good practice, IMO).

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

One other nice optional idea: give your app's launch storyboard's vc an identifier, too (like "LaunchVC"), so that you can grab it and use it's view during startup. This will provide a seamless experience for the user during launch and while you do your startup tasks.

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

Here's what my LaunchViewController looks like....

@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,例如:

Let's express the app's readiness to run the main vc with a boolean, something like:

BOOL readyToRun = startupWorkIsDone && userIsLoggedIn;

  1. 创建一个 AppStartupViewController 并将其布置在应用故事板中.
  2. 不要将任何 segue 拖到它上面,也不要使它成为凝视的 vc,只需让它漂浮在某处即可.
  3. 在故事板中 vc 的属性检查器中,将其标识符设置为AppStartupViewController".

在 AppStartupViewController.m 中,当满足 readyToRun 条件时,它可以自行关闭:

In the AppStartupViewController.m, when the readyToRun conditions have been met, it can dismiss itself:

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

现在,只要应用程序处于活动状态,它就可以检查是否准备好运行,并在需要时显示 AppStartupViewController.在 AppDelegate.h 中

Now, whenever the app becomes active, it can check for readiness to run, and present the AppStartupViewController if it's needed. In 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
    }
}

这就是答案,但有一个问题.不幸的是,主 vc 被加载(没关系)并在 AppStartupViewController 出现之前获得一个 viewWillAppear: 消息(不好).这意味着我们必须在 MainViewController.m 中传播一些额外的启动逻辑,如下所示:

That's mostly the answer, but there is one hitch. Unfortunately the main vc gets loaded (that's okay) and gets a viewWillAppear: message (not okay) before the AppStartupViewController is presented. It means we have to spread a little extra startup logic, like this, in MainViewController.m:

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

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

我希望这会有所帮助.