Comience condicionalmente en diferentes lugares en el guión gráfico desde AppDelegate

107

Tengo un guión gráfico configurado con inicio de sesión en funcionamiento y controlador de vista principal, este último es el controlador de vista al que se navega al usuario cuando el inicio de sesión es exitoso. Mi objetivo es mostrar el controlador de vista principal inmediatamente si la autenticación (almacenada en el llavero) es exitosa y mostrar el controlador de vista de inicio de sesión si la autenticación falló. Básicamente, quiero hacer esto en mi 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
}

Sé sobre el método performSegueWithIdentifier: pero este método es un método de instancia de UIViewController, por lo que no se puede llamar desde AppDelegate. ¿Cómo hago esto usando mi guión gráfico existente?

EDITAR:

El controlador de vista inicial del Storyboard ahora es un controlador de navegación que no está conectado a nada. Usé la distinción setRootViewController: porque MainIdentifier es un UITabBarController. Entonces así es como se ven mis líneas:

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

¡Las sugerencias / mejoras son bienvenidas!

mmvie
fuente

Respuestas:

25

Supongo que su guión gráfico está configurado como el "guión gráfico principal" (clave UIMainStoryboardFileen su Info.plist). En ese caso, UIKit cargará el guión gráfico y establecerá su controlador de vista inicial como el controlador de vista raíz de su ventana antes de enviarlo application:didFinishLaunchingWithOptions:a su AppDelegate.

También asumo que el controlador de vista inicial en su guión gráfico es el controlador de navegación, en el que desea insertar su controlador de vista principal o de inicio de sesión.

Puede pedirle a su ventana su controlador de vista raíz y enviarle el performSegueWithIdentifier:sender:mensaje:

NSString *segueId = success ? @"pushMain" : @"pushLogin";
[self.window.rootViewController performSegueWithIdentifier:segueId sender:self];
Rob Mayoff
fuente
1
Implementé tus líneas de código en mi aplicación: método didFinishLaunchingWithOptions:. La depuración muestra que rootViewController es de hecho el controlador de navegación inicial, sin embargo, la transición no se realiza (se muestra la barra de navegación, el resto es negro). Debo decir que el controlador de navegación inicial ya no tiene un rootViewController, solo 2 segues (StartLoginSegue y StartMainSegue).
mmvie
3
Sí, tampoco funciona para mí. ¿Por qué lo marcó como Respondido si no le funciona?
daidai
3
Creo que esta es la respuesta correcta. Necesita 1. tener una propiedad de ventana en el delegado de su aplicación y 2. llamar [[self window] makeKeyAndVisible]a application: didFinishLaunchingWithOptions: antes de intentar realizar las segues condicionales. Se supone que UIApplicationMain () envía un mensaje a makeKeyAndVisible pero lo hace solo después de didFinish ... Opciones: finaliza. Busque "Coordinación de esfuerzos entre controladores de vista" en los documentos de Apple para obtener más detalles.
edelaney05
Esta es la idea correcta, pero no funciona del todo. Vea mi respuesta para una solución funcional.
Matthew Frederick
@MatthewFrederick Su solución funcionará si el controlador inicial es un controlador de navegación, pero no si es un controlador de vista simple. La respuesta real es simplemente crear el controlador de vista raíz y de ventana usted mismo; de hecho, esto es lo que Apple recomienda en la Guía de programación del controlador de vista. Vea mi respuesta a continuación para obtener más detalles.
Followben
170

Me sorprenden algunas de las soluciones que se sugieren aquí.

Realmente no hay necesidad de controladores de navegación ficticios en su guión gráfico, ocultando vistas y disparando segues en viewDidAppear: o cualquier otro truco.

Si no tiene el guión gráfico configurado en su archivo plist, debe crear tanto la ventana como el controlador de vista raíz usted mismo :

- (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 el guión gráfico está configurado en el plist de la aplicación, la ventana y el controlador de vista raíz ya estarán configurados por la aplicación de tiempo: se llama didFinishLaunching: y se llamará a makeKeyAndVisible en la ventana.

En ese caso, es aún más 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
fuente
@AdamRabung Spot on: acababa de copiar los nombres de las variables del OP, pero actualicé mi respuesta para mayor claridad. Salud.
followben
para el caso del guión gráfico: si está utilizando UINavigationViewcontroller como su controlador de vista raíz, deberá presionar el siguiente controlador de vista.
Shirish Kumar
Esta es una forma más intuitiva para mí en lugar de pasar por un controlador de navegación de jerarquía compleja. Amo esto
Elliot Yap
Hola @followben, en mi aplicación, tengo mi rootViewController en storyBoard, es un tabBarController, y todos los VC asociados con tabBar también están diseñados en VC, así que ahora tengo un caso, donde quiero mostrar el tutorial de mi aplicación, entonces ahora, cuando se inicia mi aplicación por primera vez, quiero hacer que el VC de tutorial sea el VC raíz en lugar de tabBarcontroller y cuando finalice mi tutorial, quiero hacer que tabBarController sea el rootViewController. No entiendo cómo hacerlo
Ranjit
1
¿Qué pasa si la solicitud al servidor es asincrónica?
Lior Burg
18

SI el punto de entrada de su guión gráfico no es 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 el punto de entrada de su guión gráfico ES un UINavigationController reemplazo:

//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];
Razvan
fuente
1
Funcionado bien. Solo un comentario, ¿esto no muestra "firstViewControllerIdentifier" solo después de haber ingresado inicialmente? Entonces, ¿no debería revertirse? appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;
ammianus
@ammianus tienes razón. Deben revertirse y editarse.
Razvan
9

En el application:didFinishLaunchingWithOptionsmétodo de su AppDelegate , antes de la return YESlínea, agregue:

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

Reemplace YourStartingViewControllercon el nombre de su primera clase de controlador de vista real (la que no desea que aparezca necesariamente) yYourSegueIdentifier con el nombre real de la transición entre ese controlador inicial y el que realmente desea comenzar (el que está después de la transición ).

Envuelve ese código en un ifcondicional si no siempre quieres que suceda.

Matthew Frederick
fuente
6

Dado que ya está usando un Storyboard, puede usar esto para presentarle al usuario MyViewController, un controlador personalizado ( resumiendo la respuesta de followben un poco ).

En 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 cadena que se pasa a instantiateViewControllerWithIdentifier se refiere al ID del guión gráfico, que se puede configurar en el generador de interfaces:

ingrese la descripción de la imagen aquí

Simplemente envuelva esto en lógica según sea necesario.

Sin embargo, si está comenzando con un UINavigationController, este enfoque no le proporcionará controles de navegación.

Para 'saltar hacia adelante' desde el punto de partida de un controlador de navegación configurado a través del generador de interfaz, utilice este enfoque:

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

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

    return YES;
}
Rico Apodaca
fuente
4

¿Por qué no tener la pantalla de inicio de sesión que aparece primero, comprobar si el usuario ya ha iniciado sesión y presionar la siguiente pantalla de inmediato? Todo en ViewDidLoad.

Darren
fuente
2
De hecho, esto funciona, pero mi objetivo es mostrar la imagen de inicio siempre que la aplicación todavía esté esperando la respuesta del servidor (si el inicio de sesión fue exitoso o no). Al igual que la aplicación de Facebook ...
mmvie
2
Siempre puede tener su primera vista solo un UIImage que usa la misma imagen que su presentación y en la verificación de antecedentes para ver si ha iniciado sesión y mostrar la siguiente vista.
Darren
3

Rápida implementación de la misma:

Si lo usa UINavigationControllercomo punto de entrada en el guión gráfico

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
fuente
1

Esta es la solución que funcionó en iOS7. Para acelerar la carga inicial y no realizar ninguna carga innecesaria, tengo un UIViewcontroller completamente vacío llamado "DUMMY" en mi archivo de Storyboard. Entonces puedo usar el siguiente código:

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

Sugiero crear un nuevo MainViewController que sea el controlador de vista raíz del controlador de navegación. Para hacer eso, simplemente mantenga el control, luego arrastre la conexión entre el controlador de navegación y MainViewController, elija 'Relación - Controlador de vista raíz' en el indicador.

En MainViewController:

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

Recuerde crear segues entre MainViewController con los controladores de vista Inicio e Inicio de sesión. Espero que esto ayude. :)

thanhbinh84
fuente
0

Después de probar muchos métodos diferentes, pude resolver este problema con esto:

-(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
fuente
Si no está muy lejos de Taylor, es posible que desee refactorizar a algo más simple. Vea mi respuesta para más detalles :)
followben