Warunkowo zacznij w różnych miejscach w scenorysie z AppDelegate

107

Mam scenorys skonfigurowany z działającym logowaniem i głównym kontrolerem widoku, ten ostatni jest kontrolerem widoku, do którego przechodzi użytkownik po pomyślnym zalogowaniu. Moim celem jest natychmiastowe pokazanie kontrolera widoku głównego, jeśli uwierzytelnianie (przechowywane w pęku kluczy) powiedzie się, i pokazanie kontrolera widoku logowania, jeśli uwierzytelnienie się nie powiodło. Zasadniczo chcę to zrobić w mojej 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
}

Wiem o metodzie performSegueWithIdentifier: ale ta metoda jest metodą instancji UIViewController, więc nie można jej wywołać z poziomu AppDelegate. Jak to zrobić, korzystając z mojej istniejącej serii ujęć?

EDYTOWAĆ:

Początkowy kontroler widoku Storyboard jest teraz kontrolerem nawigacji, który nie jest z niczym połączony. Użyłem setRootViewController: rozróżnienie, ponieważ MainIdentifier to UITabBarController. A więc tak wyglądają moje linie:

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

Sugestie / ulepszenia są mile widziane!

mmvie
źródło

Odpowiedzi:

25

Zakładam, że Twój storyboard jest ustawiony jako „główna plansza” (klucz UIMainStoryboardFilew Info.plist). W takim przypadku UIKit załaduje scenorys i ustawi jego początkowy kontroler widoku jako główny kontroler widoku okna, zanim wyśle ​​go application:didFinishLaunchingWithOptions:do AppDelegate.

Zakładam również, że początkowy kontroler widoku w scenorysie to kontroler nawigacji, na który chcesz wypchnąć główny lub kontroler widoku logowania.

Możesz poprosić swoje okno o jego główny kontroler widoku i wysłać performSegueWithIdentifier:sender:do niego wiadomość:

NSString *segueId = success ? @"pushMain" : @"pushLogin";
[self.window.rootViewController performSegueWithIdentifier:segueId sender:self];
rob mayoff
źródło
1
Zaimplementowałem Twoje wiersze kodu w mojej aplikacji: didFinishLaunchingWithOptions: metoda. Debugowanie pokazuje, że rootViewController jest rzeczywiście początkowym kontrolerem nawigacji, jednak przejście nie jest wykonywane (pasek nawigacji jest wyświetlany, reszta jest czarna). Muszę powiedzieć, że początkowy kontroler nawigacji nie ma już rootViewController, tylko 2 segmenty (StartLoginSegue i StartMainSegue).
mmvie,
3
Tak, dla mnie też nie działa. Dlaczego oznaczyłeś to „Odpowiedziano”, jeśli to nie działa dla Ciebie?
daidai
3
Uważam, że to poprawna odpowiedź. Musisz mieć 1. właściwość okna w delegacie aplikacji i 2. wywołanie [[self window] makeKeyAndVisible]w aplikacji: didFinishLaunchingWithOptions: zanim spróbujesz wykonać warunkowe sekwencje. UIApplicationMain () ma wysłać komunikat makeKeyAndVisible, ale robi to dopiero po didFinish ... Opcje: kończy. Aby uzyskać szczegółowe informacje, poszukaj pozycji „Koordynacja wysiłków między kontrolerami widoku” w dokumentach Apple.
edelaney05
To jest właściwy pomysł, ale nie do końca działa. Zobacz moją odpowiedź na działające rozwiązanie.
Matthew Frederick,
@MatthewFrederick Twoje rozwiązanie będzie działać, jeśli początkowy kontroler jest kontrolerem nawigacji, ale nie, jeśli jest to zwykły kontroler widoku. Prawdziwą odpowiedzią jest po prostu samodzielne utworzenie kontrolera widoku okna i katalogu głównego - rzeczywiście to jest to, co Apple zaleca w Przewodniku programowania kontrolera widoku. Zobacz moją odpowiedź poniżej, aby uzyskać szczegółowe informacje.
followben
170

Jestem zaskoczony niektórymi proponowanymi tutaj rozwiązaniami.

Naprawdę nie ma potrzeby stosowania fałszywych kontrolerów nawigacyjnych w scenorysie, ukrywania widoków i wypalania fragmentów w widoku ViewDidAppear: ani żadnych innych hacków.

Jeśli nie masz scenorysu skonfigurowanego w pliku plist, musisz samodzielnie utworzyć zarówno okno, jak i główny kontroler widoku :

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

Jeśli scenorys jest skonfigurowany w pliku plist aplikacji, kontroler okna i widoku głównego zostanie już skonfigurowany przez aplikację czasu: didFinishLaunching: jest wywoływana, a makeKeyAndVisible zostanie wywołana w oknie za Ciebie.

W takim przypadku jest to jeszcze prostsze:

- (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
źródło
@AdamRabung Spot on - właśnie skopiowałem nazwy zmiennych OP, ale zaktualizowałem odpowiedź dla przejrzystości. Twoje zdrowie.
followben
w przypadku scenorysu: Jeśli używasz UINavigationViewcontroller jako głównego kontrolera widoku, musisz wypchnąć następny kontroler widoku.
Shirish Kumar
Jest to dla mnie bardziej intuicyjny sposób niż przechodzenie przez skomplikowany kontroler nawigacji w hierarchii. Uwielbiam to
Elliot Yap,
Witaj @followben, w mojej aplikacji mam swój rootViewController w storyBoard, jest to tabBarController, a wszystkie powiązane VC z tabBar są również zaprojektowane w VC, więc teraz mam przypadek, w którym chcę pokazać przewodnik po mojej aplikacji, więc teraz, gdy moja aplikacja jest uruchamiana po raz pierwszy, chcę, aby przewodnik VC był głównym VC zamiast tabBarcontroller, a po zakończeniu mojego przewodnika chcę ustawić tabBarController jako rootViewController. Jak to zrobić, nie rozumiem
Ranjit
1
A co, jeśli żądanie do serwera jest asynchroniczne?
Lior Burg
18

JEŚLI punktem wejścia twojego storyboardu nie jest 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;
}

JEŻELI punkt wejścia w scenorysie JEST UINavigationControllerzamiennikiem:

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

z:

//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
źródło
1
Działało dobrze. Tylko komentarz, czy to nie pokazuje „firstViewControllerIdentifier” dopiero po ich wprowadzeniu na początku? Czy więc nie należy tego odwrócić? appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;
ammianus
@ammianus masz rację. Powinny być odwrócone, a ja zredagowałem.
Razvan
9

W application:didFinishLaunchingWithOptionsmetodzie AppDelegate , przed return YESwierszem, dodaj:

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

Zastąp YourStartingViewControllernazwą swojej aktualnej klasy kontrolera pierwszego widoku (tej, której niekoniecznie chcesz się pojawić) oraz YourSegueIdentifierrzeczywistą nazwą przejścia między tym kontrolerem początkowym a tym, na którym chcesz rozpocząć (ten po przejściu ).

Zawiń ten kod w ifwarunek, jeśli nie zawsze chcesz, aby tak się stało.

Matthew Frederick
źródło
6

Biorąc pod uwagę, że używasz już Storyboard, możesz użyć tego, aby zaprezentować użytkownikowi MyViewController, niestandardowy kontroler ( podsumowanie odpowiedzi followbena ).

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

Ciąg przekazany do instantiateViewControllerWithIdentifier odwołuje się do identyfikatora serii ujęć, który można ustawić w konstruktorze interfejsu:

wprowadź opis obrazu tutaj

W razie potrzeby po prostu zawiń to w logikę.

Jeśli jednak zaczynasz od UINavigationController, to podejście nie zapewni Ci elementów sterujących nawigacją.

Aby „przeskoczyć do przodu” od punktu początkowego kontrolera nawigacyjnego skonfigurowanego za pomocą narzędzia do tworzenia interfejsu, zastosuj następujące podejście:

- (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
źródło
4

Dlaczego nie wyświetlić ekranu logowania, który pojawia się jako pierwszy, sprawdzić, czy użytkownik jest już zalogowany i od razu przesunąć następny ekran? Wszystko w pliku ViewDidLoad.

Darren
źródło
2
To rzeczywiście działa, ale moim celem jest wyświetlanie obrazu startowego, o ile aplikacja nadal czeka na odpowiedź serwera (niezależnie od tego, czy logowanie się powiodło, czy nie). Podobnie jak aplikacja na Facebooku ...
mmvie,
2
Zawsze możesz mieć swój pierwszy widok po prostu UIImage, który używa tego samego obrazu co twój splash i sprawdza w tle, czy jesteś zalogowany i wyświetla następny widok.
Darren
3

Szybka realizacja tego samego:

Jeśli używasz UINavigationControllerjako punktu wejścia w scenorysie

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
źródło
1

To rozwiązanie działało w iOS7. Aby przyspieszyć początkowe ładowanie i uniknąć niepotrzebnego ładowania, mam całkowicie pusty kontroler UIView o nazwie „DUMMY” w moim pliku Storyboard. Następnie mogę użyć następującego kodu:

- (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
źródło
0

Proponuję utworzyć nowy MainViewController, który jest głównym kontrolerem widoku kontrolera nawigacji. Aby to zrobić, po prostu przytrzymaj kontrolkę, a następnie przeciągnij połączenie między kontrolerem nawigacji a kontrolerem MainViewController, wybierz z monitu opcję `` Relacja - główny kontroler widoku ''.

W MainViewController:

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

Pamiętaj, aby utworzyć sekwencje między MainViewController z kontrolerami widoku Home i Login. Mam nadzieję że to pomoże. :)

thanhbinh84
źródło
0

Po wypróbowaniu wielu różnych metod udało mi się rozwiązać ten problem w ten sposób:

-(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
źródło
Jeśli nie jesteś zbyt daleko od Taylora, możesz chcieć zmienić coś na prostszy. Zobacz moją odpowiedź po szczegóły :)
followben