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!

mmvie
fonte

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];
rob mayoff
fonte
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;
}
seguir
fonte
@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];
Razvan
fonte
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.

Matthew Frederick
fonte
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;
}
Rich Apodaca
fonte
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.

Darren
fonte
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) 
    }
Dashrath
fonte
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;
}
Luc Bloom
fonte
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. :)

Thanhbinh84
fonte
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;
}
AddisDev
fonte
Se você não estiver muito longe de Taylor, talvez queira refatorar para algo mais simples. Veja minha resposta para detalhes :)
followben