Inizia in modo condizionale in punti diversi nello storyboard da AppDelegate


107

Ho uno storyboard impostato con accesso funzionante e controller di visualizzazione principale, quest'ultimo è il controller di visualizzazione a cui l'utente viene indirizzato quando l'accesso ha esito positivo. Il mio obiettivo è mostrare immediatamente il controller della vista principale se l'autenticazione (memorizzata nel portachiavi) ha esito positivo e mostrare il controller della vista di accesso se l'autenticazione non è riuscita. Fondamentalmente, voglio farlo nel mio 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
}

Conosco il metodo performSegueWithIdentifier: ma questo metodo è un metodo di istanza di UIViewController, quindi non richiamabile da AppDelegate. Come posso farlo usando il mio storyboard esistente?

MODIFICARE:

Il controller di visualizzazione iniziale dello Storyboard ora è un controller di navigazione che non è connesso a nulla. Ho usato setRootViewController: distinction perché MainIdentifier è un UITabBarController. Quindi questo è l'aspetto delle mie linee:

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

Suggerimenti / miglioramenti sono i benvenuti!

Risposte:


25

Presumo che il tuo storyboard sia impostato come "storyboard principale" ( UIMainStoryboardFiledigita il tuo Info.plist). In tal caso, UIKit caricherà lo storyboard e imposterà il suo controller di visualizzazione iniziale come controller di visualizzazione principale della finestra prima che venga inviato application:didFinishLaunchingWithOptions:al tuo AppDelegate.

Presumo anche che il controller di visualizzazione iniziale nello storyboard sia il controller di navigazione, su cui si desidera spingere il controller di visualizzazione principale o di accesso.

Puoi chiedere alla tua finestra il suo controller di visualizzazione principale e inviargli il performSegueWithIdentifier:sender:messaggio:

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

1
Ho implementato le tue righe di codice nella mia applicazione: didFinishLaunchingWithOptions: method. Il debug mostra che rootViewController è effettivamente il controller di navigazione iniziale, tuttavia il segue non viene eseguito (la barra di navigazione è visualizzata, il resto è nero). Devo dire che il controller di navigazione iniziale non ha più un rootViewController, solo 2 segues (StartLoginSegue e StartMainSegue).
mmvie

3
Sì, non funziona neanche per me. Perché l'hai contrassegnato con risposta se non funziona per te?
daidai

3
Credo che questa sia la risposta corretta. È necessario 1. avere una proprietà window sul delegato dell'app e 2. chiamare [[self window] makeKeyAndVisible]application: didFinishLaunchingWithOptions: prima di provare a eseguire le sequenze condizionali. Si suppone che UIApplicationMain () invii un messaggio a makeKeyAndVisible ma lo fa solo dopo didFinish ... Opzioni: finisce. Cerca "Coordinamento degli sforzi tra i controller di visualizzazione" nei documenti Apple per i dettagli.
edelaney05

Questa è l'idea giusta, ma non funziona. Vedi la mia risposta per una soluzione funzionante.
Matthew Frederick

@MatthewFrederick La tua soluzione funzionerà se il controller iniziale è un controller di navigazione, ma non se è un controller di visualizzazione normale. La vera risposta è solo creare da soli la finestra e il controller di visualizzazione root - in effetti questo è ciò che Apple consiglia nella Guida alla programmazione del controller di visualizzazione. Vedi la mia risposta di seguito per i dettagli.
followben

170

Sono sorpreso di alcune delle soluzioni suggerite qui.

Non c'è davvero bisogno di controller di navigazione fittizi nel tuo storyboard, nascondendo le visualizzazioni e sparando segues su viewDidAppear: o qualsiasi altro hack.

Se non hai lo storyboard configurato nel tuo file plist, devi creare tu stesso sia la finestra che il controller della vista principale :

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

Se lo storyboard è configurato nel plist dell'app, la finestra e il controller della vista radice saranno già configurati dall'applicazione temporale: didFinishLaunching: viene chiamato e makeKeyAndVisible verrà chiamato nella finestra per te.

In tal caso, è ancora più semplice:

- (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 - Avevo appena copiato i nomi delle variabili dell'OP, ma ho aggiornato la mia risposta per chiarezza. Saluti.
followben

per il caso dello storyboard: se stai usando UINavigationViewcontroller come controller di visualizzazione principale, dovrai eseguire il push del controller di visualizzazione successivo.
Shirish Kumar

Questo è un modo più intuitivo per me piuttosto che passare attraverso un controller di navigazione gerarchico complesso. Adoro questo
Elliot Yap

Ciao @followben, nella mia app, ho il mio rootViewController in storyBoard, è un tabBarController e tutti i VC associati con tabBar sono progettati anche in VC, quindi ora ho un caso, in cui voglio mostrare la procedura dettagliata della mia app, quindi ora, quando la mia app viene avviata per la prima volta, desidero creare una procedura dettagliata VC come VC radice anziché tabBarcontroller e al termine della procedura dettagliata, voglio rendere tabBarController come rootViewController. Come farlo non capisco
Ranjit

1
E se la richiesta al server è asincrona?
Lior Burg

18

SE il punto di ingresso dello storyboard non è 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;
}

SE il punto di ingresso dello storyboard È una UINavigationControllersostituzione:

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

con:

//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
Ha funzionato bene. Solo un commento, questo non mostra "firstViewControllerIdentifier" solo dopo che sono entrati inizialmente? Quindi non dovrebbe essere invertito? appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;
ammianus

@ammianus hai ragione. Dovrebbero essere invertiti e ho modificato.
Razvan

9

Nel application:didFinishLaunchingWithOptionsmetodo di AppDelegate , prima della return YESriga, aggiungi:

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

Sostituisci YourStartingViewControllercon il nome della tua prima classe controller di visualizzazione (quella che non vuoi che appaia necessariamente) eYourSegueIdentifier con il nome effettivo del segue tra quel controller di partenza e quello da cui vuoi iniziare effettivamente (quello dopo il segue ).

Avvolgi quel codice in un ifcondizionale se non vuoi che accada sempre.


6

Dato che stai già utilizzando uno Storyboard, puoi usarlo per presentare all'utente MyViewController, un controller personalizzato (Boiling down followben's answer un po ' ).

In 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 stringa passata a instantiateViewControllerWithIdentifier fa riferimento all'ID Storyboard, che può essere impostato nel generatore di interfacce:

inserisci qui la descrizione dell'immagine

Basta avvolgere questo nella logica secondo necessità.

Se stai iniziando con un UINavigationController, però, questo approccio non ti darà controlli di navigazione.

Per "saltare in avanti" dal punto di partenza di un controller di navigazione impostato tramite il generatore di interfacce, utilizzare questo approccio:

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

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

    return YES;
}

4

Perché non avere la schermata di accesso che appare per prima, controllare se l'utente è già connesso e spingere subito la schermata successiva? Tutto nel ViewDidLoad.


2
Funziona davvero, ma il mio obiettivo è mostrare l'immagine di avvio finché l'app è ancora in attesa della risposta del server (indipendentemente dal fatto che l'accesso sia riuscito o meno). Proprio come l'app di Facebook ...
mmvie

2
Potresti sempre avere la tua prima vista solo un UIImage che usa la stessa immagine del tuo splash e in background controllare per vedere se sei loggato e visualizzare la vista successiva.
Darren

3

Rapida implementazione dello stesso:

Se usi UINavigationControllercome punto di ingresso nello 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) 
    }

1

Questa è la soluzione che ha funzionato su iOS7. Per velocizzare il caricamento iniziale e non fare alcun caricamento non necessario, ho un UIViewcontroller completamente vuoto chiamato "DUMMY" nel mio file Storyboard. Quindi posso usare il seguente codice:

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

Suggerisco di creare un nuovo MainViewController che sia Root View Controller di Navigation Controller. Per farlo, tieni premuto il controllo, quindi trascina la connessione tra Navigation Controller e MainViewController, scegli "Relationship - Root View Controller" dal prompt.

In MainViewController:

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

Ricordarsi di creare passaggi tra MainViewController con i controller di visualizzazione Home e Login. Spero che questo ti aiuti. :)


0

Dopo aver provato molti metodi diversi, sono stato in grado di risolvere questo problema con questo:

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

Se non sei troppo lontano da Taylor, potresti voler eseguire il refactoring in qualcosa di più semplice. Vedi la mia risposta per i dettagli :)
followben
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.