4

私はこの質問をさまざまな形で見てきましたが、明確な答えはありません。ここで質問して回答します。私のアプリは、起動時に作業を行う必要があります... inits、いくつかのネットワーク呼び出し、ログインなど。それが完了するまで、メインのビューコントローラーを動作させたくありません。これを行うための良いパターンは何ですか?

要件:

  • iOS5+、絵コンテ、ARC。
  • スタートアップ作業が完了するまで、メイン VC のビューは表示されません。
  • 起動作業が完了したときに、メイン VC への遷移スタイルを選択したい。
  • ストーリーボードでできるだけ多くのレイアウトを行いたい。
  • コードは明確な場所に明確に含まれている必要があります。
4

2 に答える 2

13

編集、2017 年 7 月 最初の執筆以来、私は自分のプラクティスを自分のビュー コントローラーにスタートアップ タスクを与えるものに変更しました。その VC で、起動条件をチェックし、必要に応じて「ビジー」な UI を表示するなどします。起動時の状態に基づいて、ウィンドウのルート VC を設定します。

OP の問題を解決することに加えて、このアプローチには、起動 UI と UI 遷移をより適切に制御できるという追加の利点があります。方法は次のとおりです。

メインのストーリーボードで、という新しい VC を追加し、LaunchViewControllerそれをアプリの初期 VC にします。アプリの「実際の」初期 vc に「AppUI」などの識別子を付けます (識別子は IB の [ID] タブにあります)。

メインの UI フロー (サインアップ/ログイン、チュートリアルなど) の開始点である他の vc を識別し、これらの説明的な識別子も提供します。(各フローを独自のストーリーボードに保持することを好む人もいます。それは良い習慣です、IMO)。

もう 1 つの優れたオプションのアイデア: アプリの起動ストーリーボードの 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. セグエをドラッグしないでください。また、凝視している 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
    }
}

それがほとんどの答えですが、問題が 1 つあります。残念ながら、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. ローディング コントローラーをストーリーボードに追加し、スタイル モーダルの名前付きセグエにリンクします。

  3. メイン コントローラーの viewWillAppear で、セグエをトリガーします (アプリの実行ごとに 1 回のみ)。

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

おそらくナビゲーションコントローラーのアニメーションがまだ完了しておらず、unbalanced calls to begin/end appearance transitions

于 2014-07-15T14:32:41.147 に答える