Comece condicionalmente em locais diferentes no storyboard de AppDelegate


107

Eu tenho um storyboard configurado com login funcional e controlador de visualização principal, o último é o controlador de visualização para o qual o usuário é navegado quando o login é bem-sucedido. Meu objetivo é mostrar o controlador de visualização principal imediatamente se a autenticação (armazenada no keychain) for bem-sucedida e mostrar o controlador de visualização de login se a autenticação falhar. Basicamente, quero fazer isso no meu 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
}

Eu sei sobre o método performSegueWithIdentifier: mas este método é um método de instância de UIViewController, portanto, não pode ser chamado de dentro de AppDelegate. Como faço isso usando meu storyboard existente?

EDITAR:

O controlador de visualização inicial do Storyboard agora é um controlador de navegação que não está conectado a nada. Usei setRootViewController: distinção porque MainIdentifier é um UITabBarController. Então é assim que minhas linhas se parecem:

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

Sugestões / melhorias são bem-vindas!

Respostas:


25

Presumo que seu storyboard esteja definido como o "storyboard principal" (digite UIMainStoryboardFileseu Info.plist). Nesse caso, o UIKit carregará o storyboard e definirá seu controlador de visualização inicial como o controlador de visualização raiz da janela antes de enviar application:didFinishLaunchingWithOptions:para o AppDelegate.

Também presumo que o controlador de visualização inicial em seu storyboard seja o controlador de navegação, no qual você deseja enviar seu controlador de visualização principal ou de login.

Você pode solicitar à sua janela o controlador de visualização raiz e enviar a performSegueWithIdentifier:sender:mensagem para ela:

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

1
Implementei suas linhas de código em meu aplicativo: método didFinishLaunchingWithOptions:. A depuração mostra que o rootViewController é de fato o controlador de navegação inicial, mas a segue não é realizada (a barra de navegação está sendo exibida, o resto está preto). Devo dizer que o controlador de navegação inicial não tem mais um rootViewController, apenas 2 segues (StartLoginSegue e StartMainSegue).
— mmvie

3
Sim, não está funcionando para mim também. Por que você marcou como Respondido se não funciona para você?
— daidai de

3
Eu acredito que esta é a resposta correta. Você precisa 1. ter uma propriedade de janela em seu delegado de aplicativo e 2. chamar [[self window] makeKeyAndVisible]em aplicativo: didFinishLaunchingWithOptions: antes de tentar realizar as segues condicionais. UIApplicationMain () deve enviar a mensagem makeKeyAndVisible, mas o faz somente após didFinish ... Options: terminar. Procure "Coordenando esforços entre controladores de visualização" nos documentos da Apple para obter detalhes.
— edelaney05

Esta é a ideia certa, mas não funciona. Veja minha resposta para uma solução de trabalho.
— Matthew Frederick

@MatthewFrederick Sua solução funcionará se o controlador inicial for um controlador de navegação, mas não se for um controlador de visualização simples. A resposta real é apenas criar você mesmo a janela e o controlador de visualização root - de fato, isso é o que a Apple recomenda no Guia de programação do controlador de visualização. Veja minha resposta abaixo para detalhes.
— followben

170

Estou surpreso com algumas das soluções sugeridas aqui.

Não há realmente nenhuma necessidade de controladores de navegação fictícios em seu storyboard, ocultando visualizações e disparando segues em viewDidAppear: ou quaisquer outros hacks.

Se você não tiver o storyboard configurado em seu arquivo plist, deve criar você mesmo a janela e o controlador de visualização raiz :

- (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 o storyboard estiver configurado no plist do aplicativo, a janela e o controlador de visualização raiz já estarão configurados no momento em que o aplicativo: didFinishLaunching: for chamado e makeKeyAndVisible será chamado na janela para você.

Nesse caso, é ainda mais simples:

- (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 - acabei de copiar os nomes das variáveis ​​do OP, mas atualizei minha resposta para maior clareza. Felicidades.
— seguimento

para o caso do storyboard: Se você estiver usando UINavigationViewcontroller como seu controlador de visualização raiz, será necessário enviar o próximo controlador de visualização.
— Shirish Kumar

Esta é uma forma mais intuitiva para mim, em vez de usar o controlador de navegação de hierarquia complexa. Amei isso
— Elliot Yap

Olá @followben, em meu aplicativo, tenho meu rootViewController no storyBoard, é um tabBarController, e todos os VCs associados com tabBar também são projetados em VC, então agora tenho um caso em que desejo mostrar o passo a passo do meu aplicativo, agora, quando meu aplicativo é iniciado pela primeira vez, eu quero fazer um guia de VC como o VC raiz em vez de tabBarcontroller e quando meu passo a passo terminar, eu quero fazer tabBarController como o rootViewController. Não estou entendendo como fazer
— Ranjit

1
E se a solicitação ao servidor for assíncrona?
— Lior Burg

18

SE o ponto de entrada do seu storyboard não for 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 o ponto de entrada do seu storyboard FOR uma UINavigationControllersubstituição:

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

com:

//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
Funcionou bem. Apenas um comentário, isso não mostra "firstViewControllerIdentifier" somente depois que eles inseriram inicialmente? Portanto, não deveria ser revertido? appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;
— ammianus

@ammianus você está correto. Eles devem ser revertidos e editados.
— Razvan

9

No application:didFinishLaunchingWithOptionsmétodo do AppDelegate , antes da return YESlinha, adicione:

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

Substitua YourStartingViewControllerpelo nome da sua classe de controlador de primeira visualização real (aquela que você não quer que apareça necessariamente) e YourSegueIdentifierpelo nome real do segue entre aquele controlador inicial e aquele em que você deseja realmente iniciar (aquele após o segue )

Envolva esse código em uma ifcondicional se você não quiser que isso aconteça sempre.


6

Dado que você já está usando um Storyboard, você pode usá-lo para apresentar ao usuário o MyViewController, um controlador personalizado ( resumindo a resposta seguinte ).

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

A string passada para instantiateViewControllerWithIdentifier refere-se ao ID do Storyboard, que pode ser definido no construtor de interface:

insira a descrição da imagem aqui

Basta envolver isso na lógica conforme necessário.

Se você está começando com um UINavigationController, entretanto, esta abordagem não fornecerá controles de navegação.

Para 'avançar' a partir do ponto de partida de um controlador de navegação configurado através do construtor de interface, use esta abordagem:

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

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

    return YES;
}

4

Por que não ter a tela de login que aparece primeiro, verificar se o usuário já está logado e avançar para a próxima tela imediatamente? Tudo no ViewDidLoad.


2
Isso funciona de fato, mas meu objetivo é mostrar a imagem de inicialização enquanto o aplicativo ainda estiver aguardando a resposta do servidor (se o login foi bem-sucedido ou não). Assim como o aplicativo do Facebook ...
— mmvie

2
Você sempre pode ter sua primeira visualização, apenas um UIImage que usa a mesma imagem do seu splash e, em segundo plano, verificar se está conectado e exibir a próxima visualização.
— Darren

3

Implementação rápida do mesmo:

Se você usar UINavigationControllercomo ponto de entrada no 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

Esta é a solução que funcionou no iOS7. Para acelerar o carregamento inicial e não fazer nenhum carregamento desnecessário, tenho um controlador UIView completamente vazio chamado "DUMMY" em meu arquivo Storyboard. Então posso usar o seguinte 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;
}

0

Eu sugiro criar um novo MainViewController que seja o Root View Controller do Navigation Controller. Para fazer isso, basta segurar o controle e arrastar a conexão entre o Controlador de navegação e o MainViewController e escolher 'Relacionamento - Controlador de visualização raiz' no prompt.

Em MainViewController:

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

Lembre-se de criar segues entre MainViewController com os controladores de visualização Home e Login. Espero que isto ajude. :)


0

Depois de tentar vários métodos diferentes, consegui resolver este problema com este:

-(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 você não estiver muito longe de Taylor, talvez queira refatorar para algo mais simples. Veja minha resposta para detalhes :)
— followben
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.