4

我已经以各种形式看到了这个问题,但没有明确的答案。我要在这里提问和回答。我的应用程序需要在启动时工作......初始化、一些网络调用、登录等。我不希望我的主视图控制器在完成之前工作。这样做的好模式是什么?

要求:

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

2 回答 2

13

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

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

在您的主故事板中,添加一个新的 VC,称为LaunchViewController并使其成为应用程序的初始 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 并将其放置在应用故事板中。
  2. 不要将任何segue拖到它上面,也不要让它成为凝视的vc,让它漂浮在某个地方。
  3. 在情节提要中 vc 的属性检查器中,将其标识符设置为“AppStartupViewController”。

在 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
    }
}

我希望这是有帮助的。

于 2012-07-28T02:26:52.423 回答
0

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

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

  2. 将您的加载控制器添加到情节提要中,并与命名的 segue of style 模态链接。

  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

于 2014-07-15T14:32:41.147 回答