Commencer conditionnellement à différents endroits dans le storyboard d'AppDelegate

107

J'ai un storyboard configuré avec une connexion fonctionnelle et un contrôleur de vue principal, ce dernier est le contrôleur de vue vers lequel l'utilisateur est dirigé lorsque la connexion est réussie. Mon objectif est d'afficher immédiatement le contrôleur de vue principal si l'authentification (stockée dans le trousseau) est réussie, et d'afficher le contrôleur de vue de connexion si l'authentification a échoué. Fondamentalement, je veux faire cela dans mon 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
}

Je connais la méthode performSegueWithIdentifier: mais cette méthode est une méthode d'instance de UIViewController, donc non appelable depuis AppDelegate. Comment puis-je faire cela en utilisant mon storyboard existant?

ÉDITER:

Le contrôleur de vue initial du Storyboard est maintenant un contrôleur de navigation qui n'est connecté à rien. J'ai utilisé le setRootViewController: distinction car MainIdentifier est un UITabBarController. Alors voici à quoi ressemblent mes lignes:

- (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;
}

Les suggestions / améliorations sont les bienvenues!

mmvie
la source

Réponses:

25

Je suppose que votre storyboard est défini comme le "storyboard principal" ( UIMainStoryboardFileentrez votre Info.plist). Dans ce cas, UIKit chargera le storyboard et définira son contrôleur de vue initial comme contrôleur de vue racine de votre fenêtre avant de l'envoyer application:didFinishLaunchingWithOptions:à votre AppDelegate.

Je suppose également que le contrôleur de vue initial de votre storyboard est le contrôleur de navigation, sur lequel vous souhaitez pousser votre contrôleur de vue principal ou de connexion.

Vous pouvez demander à votre fenêtre son contrôleur de vue racine et lui envoyer le performSegueWithIdentifier:sender:message:

NSString *segueId = success ? @"pushMain" : @"pushLogin";
[self.window.rootViewController performSegueWithIdentifier:segueId sender:self];
Rob Mayoff
la source
1
J'ai implémenté vos lignes de code dans mon application: didFinishLaunchingWithOptions: method. Le débogage montre que rootViewController est en effet le contrôleur de navigation initial, le segue n'est cependant pas effectué (la barre de navigation est affichée, le reste est noir). Je dois dire que le contrôleur de navigation initial n'a plus de rootViewController, seulement 2 segues (StartLoginSegue et StartMainSegue).
mmvie
3
Oui, je ne travaille pas non plus pour moi. Pourquoi l'avez-vous marqué comme Réponse si cela ne fonctionne pas pour vous?
daidai
3
Je pense que c'est la bonne réponse. Vous devez 1. avoir une propriété window sur votre délégué d'application et 2. appeler l' [[self window] makeKeyAndVisible]application: didFinishLaunchingWithOptions: avant d'essayer d'effectuer les séquences conditionnelles. UIApplicationMain () est supposé envoyer un message à makeKeyAndVisible mais ne le fait qu'après didFinish ... Options: se termine. Recherchez «Coordination des efforts entre les contrôleurs de vue» dans la documentation Apple pour plus de détails.
edelaney05
C'est la bonne idée, mais cela ne fonctionne pas tout à fait. Voir ma réponse pour une solution de travail.
Matthew Frederick
@MatthewFrederick Votre solution fonctionnera si le contrôleur initial est un contrôleur de navigation, mais pas s'il s'agit d'un contrôleur en clair. La vraie réponse est simplement de créer vous-même la fenêtre et le contrôleur de vue racine - en effet, c'est ce qu'Apple recommande dans le Guide de programmation de View Controller. Voir ma réponse ci-dessous pour plus de détails.
suivez
170

Je suis surpris de certaines des solutions proposées ici.

Il n'y a vraiment pas besoin de contrôleurs de navigation factices dans votre storyboard, de masquer les vues et de déclencher des segues sur viewDidAppear: ou tout autre hacks.

Si vous n'avez pas configuré le storyboard dans votre fichier plist, vous devez créer vous-même la fenêtre et le contrôleur de vue racine :

- (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;
}

Si le storyboard est configuré dans le plist de l'application, la fenêtre et le contrôleur de vue racine seront déjà configurés par l'application de temps: didFinishLaunching: est appelée, et makeKeyAndVisible sera appelé sur la fenêtre pour vous.

Dans ce cas, c'est encore plus simple:

- (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;
}
Followben
la source
@AdamRabung Spot on - Je viens de copier les noms des variables de l'OP, mais j'ai mis à jour ma réponse pour plus de clarté. À votre santé.
suivez le
pour le cas du storyboard: si vous utilisez UINavigationViewcontroller comme contrôleur de vue racine, vous devrez pousser le contrôleur de vue suivant.
Shirish Kumar
C'est un moyen plus intuitif pour moi que de passer par un contrôleur de navigation hiérarchique complexe. J'adore ça
Elliot Yap
Bonjour @followben, dans mon application, j'ai mon rootViewController dans storyBoard, c'est un tabBarController, et tous les VC associés avec tabBar sont également conçus dans VC, alors maintenant j'ai un cas, où je veux montrer la procédure pas à pas de mon application, donc maintenant, lorsque mon application est lancée pour la première fois, je souhaite créer un VC pas à pas en tant que VC racine au lieu de tabBarcontroller et lorsque ma procédure pas à pas se termine, je veux faire de tabBarController le rootViewController. Comment faire je ne comprends pas
Ranjit
1
Que faire si la demande au serveur est asynchrone?
Lior Burg
18

SI le point d'entrée de votre storyboard n'est pas un 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;
}

SI le point d'entrée de votre storyboard est un UINavigationControllerremplacement:

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

avec:

//IF YOU USE A NAVIGATION CONTROLLER AS THE ENTRY POINT IN YOUR STORYBOARD:
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
[navController pushViewController:presentedViewController animated:NO];
Razvan
la source
1
A bien fonctionné. Juste un commentaire, cela ne montre-t-il pas "firstViewControllerIdentifier" uniquement après leur entrée initiale? Alors ne devrait-il pas être inversé? appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;
ammianus
@ammianus vous avez raison. Ils devraient être inversés et j'ai édité.
Razvan
9

Dans la application:didFinishLaunchingWithOptionsméthode de votre AppDelegate , avant la return YESligne, ajoutez:

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

Remplacez YourStartingViewControllerpar le nom de votre première classe de contrôleur de vue réelle (celle que vous ne voulez pas nécessairement voir apparaître) etYourSegueIdentifier par le nom réel de la segue entre ce contrôleur de départ et celui sur lequel vous voulez réellement démarrer (celui qui suit la séquence ).

Enveloppez ce code dans un ifconditionnel si vous ne voulez pas toujours que cela se produise.

Matthieu Frederick
la source
6

Étant donné que vous utilisez déjà un Storyboard, vous pouvez l'utiliser pour présenter à l'utilisateur MyViewController, un contrôleur personnalisé (Boiling down followben's answer un peu ).

Dans 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;
}

La chaîne transmise à instantiateViewControllerWithIdentifier fait référence à l'ID de Storyboard, qui peut être défini dans le générateur d'interface:

entrez la description de l'image ici

Enveloppez simplement ceci dans la logique au besoin.

Si vous commencez avec un UINavigationController, cependant, cette approche ne vous donnera pas de contrôles de navigation.

Pour `` sauter en avant '' à partir du point de départ d'un contrôleur de navigation configuré via le générateur d'interface, utilisez cette approche:

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

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

    return YES;
}
Rich Apodaca
la source
4

Pourquoi ne pas avoir l'écran de connexion qui apparaît en premier, vérifier si l'utilisateur est déjà connecté et pousser l'écran suivant tout de suite? Le tout dans ViewDidLoad.

Darren
la source
2
Cela fonctionne en effet, mais mon objectif est d'afficher l'image de lancement tant que l'application attend toujours la réponse du serveur (que la connexion ait réussi ou non). Tout comme l'application Facebook ...
mmvie
2
Vous pouvez toujours avoir votre première vue juste une UIImage qui utilise la même image que votre splash et dans la vérification en arrière-plan pour voir si vous êtes connecté et afficher la vue suivante.
Darren
3

Mise en œuvre rapide de la même chose:

Si vous utilisez UINavigationControllercomme point d'entrée dans le storyboard

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) 
    }
Dashrath
la source
1

C'est la solution qui a fonctionné sous iOS7. Pour accélérer le chargement initial et ne pas effectuer de chargement inutile, j'ai un UIViewcontroller complètement vide appelé "DUMMY" dans mon fichier Storyboard. Ensuite, je peux utiliser le code suivant:

- (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;
}
Luc Bloom
la source
0

Je suggère de créer un nouveau MainViewController qui est le contrôleur de vue racine du contrôleur de navigation. Pour ce faire, maintenez simplement le contrôle, puis faites glisser la connexion entre le contrôleur de navigation et MainViewController, choisissez «Relation - Contrôleur de vue racine» à partir de l'invite.

Dans MainViewController:

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

N'oubliez pas de créer des segments entre MainViewController avec les contrôleurs de vue Accueil et Connexion. J'espère que cela t'aides. :)

thanhbinh84
la source
0

Après avoir essayé de nombreuses méthodes différentes, j'ai pu résoudre ce problème avec ceci:

-(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;
}
AddisDev
la source
Si vous n'êtes pas trop loin sur Taylor, vous voudrez peut-être refactoriser quelque chose de plus simple. Voir ma réponse pour plus de détails :)
suivez