AppDelegateからストーリーボードの別の場所から条件付きで開始する


107

ストーリーボードに、有効なログインとメインビューコントローラーを設定しました。後者は、ログインが成功したときにユーザーが移動するビューコントローラーです。私の目的は、認証(キーチェーンに格納されている)が成功した場合はメインビューコントローラをすぐに表示し、認証が失敗した場合はログインビューコントローラを表示することです。基本的に、私はこれをAppDelegateで実行したいと思います。

// url request & response work fine, assume success is a BOOL here
// that indicates whether login was successful or not

if (success) {
          // 'push' main view controller
} else {
          // 'push' login view controller
}

私はメソッドperformSegueWithIdentifierについて知っています。ただし、このメソッドはUIViewControllerのインスタンスメソッドであるため、AppDelegate内から呼び出すことはできません。既存のストーリーボードを使用してこれを行うにはどうすればよいですか?

編集:

Storyboardの最初のビューコントローラは、何にも接続されていないナビゲーションコントローラになりました。MainIdentifierはUITabBarControllerであるため、setRootViewController:の区別を使用しました。それからこれは私の線がどのように見えるかです:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{        
    BOOL isLoggedIn = ...;    // got from server response

    NSString *segueId = isLoggedIn ? @"MainIdentifier" : @"LoginIdentifier";
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UIViewController *initViewController = [storyboard instantiateViewControllerWithIdentifier:segueId];

    if (isLoggedIn) {
        [self.window setRootViewController:initViewController];
    } else {
        [(UINavigationController *)self.window.rootViewController pushViewController:initViewController animated:NO];
    }

    return YES;
}

提案/改善は大歓迎です!

回答:


25

ストーリーボードが「メインストーリーボード」(UIMainStoryboardFileInfo.plistのキー)として設定されていると思います。その場合、UIKitはストーリーボードをロードし、AppDelegateに送信application:didFinishLaunchingWithOptions:する前に、その初期のビューコントローラーをウィンドウのルートビューコントローラーとして設定します。

また、ストーリーボードの最初のビューコントローラーは、メインまたはログインビューコントローラーをプッシュするナビゲーションコントローラーであると想定しています。

ウィンドウにルートビューコントローラーを要求し、performSegueWithIdentifier:sender:メッセージを送信できます。

NSString *segueId = success ? @"pushMain" : @"pushLogin";
[self.window.rootViewController performSegueWithIdentifier:segueId sender:self];

1
私はapplication:didFinishLaunchingWithOptions:メソッドにコード行を実装しました。デバッグすると、rootViewControllerが実際に初期ナビゲーションコントローラーであることが示されますが、セグエは実行されません(ナビゲーションバーが表示され、残りは黒です)。最初のナビゲーションコントローラーにはrootViewControllerがなく、2つのセグエ(StartLoginSegueとStartMainSegue)しかないことを言わなければなりません。
mmvie

3
うん、私のためにも働いていません。うまくいかなかったのに、なぜ回答済みとマークしたのですか?
daidai

3
これが正解だと思います。[[self window] makeKeyAndVisible]条件付きセグエを実行する前に、1。アプリのデリゲートにウィンドウプロパティを設定し、2。application:didFinishLaunchingWithOptions:を呼び出す必要があります。UIApplicationMain()は、makeKeyAndVisibleにメッセージを送ることを想定していますが、didFinish ... Options:が終了した後にのみメッセージを送信します。詳細については、Appleのドキュメントで「View Controller間の努力の調整」を探してください。
edelaney05 2012年

これは正しい考えですが、うまくいきません。有効な解決策については、私の回答を参照してください。
マシューフレデリック

@MatthewFrederickこのソリューションは、初期コントローラーがナビゲーションコントローラーの場合は機能しますが、プレーンビューコントローラーの場合は機能しません。本当の答えは、ウィンドウとルートビューコントローラーを自分で作成することです-実際、これはビューコントローラープログラミングガイドでAppleが推奨していることです。詳細については、以下の私の回答を参照してください。
followben

170

ここで提案されている解決策のいくつかに驚いています。

ストーリーボードにダミーのナビゲーションコントローラーを追加したり、viewDidAppearでビューを非表示にしたり、セグエを起動したりする必要はありません。

plistファイルでストーリーボードを構成していない場合は、ウィンドウとルートビューコントローラーの両方を自分で作成する必要があります

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{        
    BOOL isLoggedIn = ...;    // from your server response

    NSString *storyboardId = isLoggedIn ? @"MainIdentifier" : @"LoginIdentifier";
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
    UIViewController *initViewController = [storyboard instantiateViewControllerWithIdentifier:storyboardId];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = initViewController;
    [self.window makeKeyAndVisible];

    return YES;
}

ストーリーボードアプリのplistで構成されている場合、ウィンドウとルートビューコントローラーは、application:didFinishLaunching:が呼び出された時点で既に設定されており、makeKeyAndVisibleがウィンドウで呼び出されます。

その場合、それはさらに簡単です:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{        
    BOOL isLoggedIn = ...;    // from your server response

    NSString *storyboardId = isLoggedIn ? @"MainIdentifier" : @"LoginIdentifier";
    self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:storyboardId];

    return YES;
}

@AdamRabung Spot on-OPの変数名をコピーしたところですが、わかりやすくするために回答を更新しました。乾杯。
followben

ストーリーボードの場合:ルートビューコントローラとしてUINavigationViewcontrollerを使用している場合は、次のビューコントローラをプッシュする必要があります。
Shirish Kumar 2013

これは、複雑な階層ナビゲーションコントローラーを経由するよりも、私にとってより直感的な方法です。これが大好き
エリオットヤップ

こんにちは@followben、私のアプリでは、storyBoardにrootViewControllerがあり、そのtabBarControllerがあります。tabBarに関連付けられているすべてのVCもVCで設計されているため、アプリのウォークスルーを表示したい場合があるので、アプリを最初に起動したとき、tabBarcontrollerではなく、ウォークスルーVCをルートVCにしたいと思います。ウォークスルーが終了したら、tabBarControllerをrootViewControllerにしたいと思います。それを行う方法私は理解していません
Ranjit

1
サーバーへのリクエストが非同期の場合はどうなりますか?
Lior Burg

18

ストーリーボードのエントリポイントがでない場合UINavigationController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {


    //Your View Controller Identifiers defined in Interface Builder
    NSString *firstViewControllerIdentifier  = @"LoginViewController";
    NSString *secondViewControllerIdentifier = @"MainMenuViewController";

    //check if the key exists and its value
    BOOL appHasLaunchedOnce = [[NSUserDefaults standardUserDefaults] boolForKey:@"appHasLaunchedOnce"];

    //if the key doesn't exist or its value is NO
    if (!appHasLaunchedOnce) {
        //set its value to YES
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"appHasLaunchedOnce"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }

    //check which view controller identifier should be used
    NSString *viewControllerIdentifier = appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;

    //IF THE STORYBOARD EXISTS IN YOUR INFO.PLIST FILE AND YOU USE A SINGLE STORYBOARD
    UIStoryboard *storyboard = self.window.rootViewController.storyboard;

    //IF THE STORYBOARD DOESN'T EXIST IN YOUR INFO.PLIST FILE OR IF YOU USE MULTIPLE STORYBOARDS
    //UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"YOUR_STORYBOARD_FILE_NAME" bundle:nil];

    //instantiate the view controller
    UIViewController *presentedViewController = [storyboard instantiateViewControllerWithIdentifier:viewControllerIdentifier];

    //IF YOU DON'T USE A NAVIGATION CONTROLLER:
    [self.window setRootViewController:presentedViewController];

    return YES;
}

ストーリーボードのエントリポイントがUINavigationController置き換えの場合:

//IF YOU DON'T USE A NAVIGATION CONTROLLER:
[self.window setRootViewController:presentedViewController];

と:

//IF YOU USE A NAVIGATION CONTROLLER AS THE ENTRY POINT IN YOUR STORYBOARD:
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
[navController pushViewController:presentedViewController animated:NO];

1
うまくいきました。ただのコメントです。これは、最初に入力した後にのみ「firstViewControllerIdentifier」を表示しませんか?それで、逆にしてはいけませんか?appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;
ammianus 2017

@ammianusあなたは正しい。それらを逆にして編集する必要があります。
Razvan 2017

9

AppDelegateのapplication:didFinishLaunchingWithOptionsメソッドで、return YES行の前に以下を追加します。

UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
YourStartingViewController *yourStartingViewController = [[navigationController viewControllers] objectAtIndex:0];
[yourStartingViewController performSegueWithIdentifier:@"YourSegueIdentifier" sender:self];

YourStartingViewController実際の最初のビューコントローラークラスの名前(必ずしも表示したくないもの)とYourSegueIdentifier、その開始コントローラーと実際に開始したいものの間のセグエの実際の名前(セグエの後の名前)に置き換えます。)。

if常に発生させたくない場合は、そのコードを条件付きで囲みます。


6

Storyboardを既に使用している場合、これを使用して、カスタムコントローラーであるMyViewControllerをユーザーに提示できます(followbenの答えを少し煮詰めます)。

ではAppDelegate.m

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    MyCustomViewController *controller = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:@"MyCustomViewController"];

    // now configure the controller with a model, etc.

    self.window.rootViewController = controller;

    return YES;
}

instantiateViewControllerWithIdentifierに渡される文字列は、インターフェースビルダーで設定できるストーリーボードIDを参照します。

ここに画像の説明を入力してください

必要に応じて、これをロジックでラップしてください。

ただし、UINavigationControllerから始めている場合、このアプローチではナビゲーションコントロールは提供されません。

インターフェイスビルダーを介して設定されたナビゲーションコントローラーの開始点から「ジャンプ」するには、次のアプローチを使用します。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UINavigationController *navigation = (UINavigationController *) self.window.rootViewController;

    [navigation.visibleViewController performSegueWithIdentifier:@"my-named-segue" sender:nil];

    return YES;
}

4

最初に表示されるログイン画面を使用しないでください。ユーザーがすでにログインしているかどうかを確認し、次の画面をすぐに押してください。ViewDidLoad内のすべて。


2
これは実際に機能しますが、私の目的は、アプリがサーバーの応答を待機している限り(ログインが成功したかどうかにかかわらず)起動イメージを表示することです。Facebookアプリのように...
mmvie '12 / 12/12

2
常に最初のビューを、スプラッシュと同じ画像を使用するUIImageだけにして、バックグラウンドでチェックして、ログインしているかどうかを確認し、次のビューを表示することができます。
ダレン

3

同じの迅速な実装:

UINavigationControllerストーリーボードのエントリポイントとして使用する場合

let storyboard = UIStoryboard(name: "Main", bundle: nil)

var rootViewController = self.window!.rootViewController as! UINavigationController;

    if(loginCondition == true){

         let profileController = storyboard.instantiateViewControllerWithIdentifier("ProfileController") as? ProfileController  
         rootViewController.pushViewController(profileController!, animated: true) 
    }
    else {

         let loginController =   storyboard.instantiateViewControllerWithIdentifier("LoginController") as? LoginController 
         rootViewController.pushViewController(loginController!, animated: true) 
    }

1

これはn iOS7で機能したソリューションです。初期読み込みを高速化し、不要な読み込みを行わないために、ストーリーボードファイルに「DUMMY」と呼ばれる完全に空のUIViewcontrollerがあります。次に、次のコードを使用できます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];

    NSString* controllerId = @"Publications";
    if (![NSUserDefaults.standardUserDefaults boolForKey:@"hasSeenIntroduction"])
    {
        controllerId = @"Introduction";
    }
    else if (![NSUserDefaults.standardUserDefaults boolForKey:@"hasDonePersonalizationOrLogin"])
    {
        controllerId = @"PersonalizeIntro";
    }

    if ([AppDelegate isLuc])
    {
        controllerId = @"LoginStart";
    }

    if ([AppDelegate isBart] || [AppDelegate isBartiPhone4])
    {
        controllerId = @"Publications";
    }

    UIViewController* controller = [storyboard instantiateViewControllerWithIdentifier:controllerId];
    self.window.rootViewController = controller;

    return YES;
}

0

ナビゲーションコントローラーのルートビューコントローラーである新しいMainViewControllerを作成することをお勧めします。そのためには、コントロールを押したまま、Navigation ControllerとMainViewControllerの間の接続をドラッグし、プロンプトから[Relationship-Root View Controller]を選択します。

MainViewControllerで:

- (void)viewDidLoad
{
    [super viewDidLoad];
    if (isLoggedIn) {
        [self performSegueWithIdentifier:@"HomeSegue" sender:nil];
    } else {
        [self performSegueWithIdentifier:@"LoginSegue" sender:nil];
    }
}

MainViewControllerとHomeビューコントローラおよびLoginビューコントローラの間にセグエを作成することを忘れないでください。お役に立てれば。:)


0

多くの異なる方法を試した後、私はこれでこの問題を解決することができました:

-(void)viewWillAppear:(BOOL)animated {

    // Check if user is already logged in
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    if ([[prefs objectForKey:@"log"] intValue] == 1) {
        self.view.hidden = YES;
    }
}

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

    // Check if user is already logged in
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    if ([[prefs objectForKey:@"log"] intValue] == 1) {
        [self performSegueWithIdentifier:@"homeSeg3" sender:self];
    }
}

-(void)viewDidUnload {
    self.view.hidden = NO;
}

テイラーに沿ってそれほど遠くない場合は、もっと簡単なものにリファクタリングしたいかもしれません。詳細については私の答えを参照してください:)
followben
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.