Условно начать в разных местах раскадровки из AppDelegate


107

У меня есть раскадровка, настроенная с рабочим логином и контроллером основного представления, последний является контроллером представления, к которому пользователь переходит в случае успешного входа в систему. Моя цель - немедленно показать главный контроллер представления, если аутентификация (хранящаяся в цепочке ключей) прошла успешно, и показать контроллер представления входа, если аутентификация не удалась. По сути, я хочу сделать это в своем 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
}

Я знаю о методе performSegueWithIdentifier: но этот метод является методом экземпляра UIViewController, поэтому его нельзя вызвать из AppDelegate. Как мне это сделать, используя существующую раскадровку ??

РЕДАКТИРОВАТЬ:

Начальный контроллер представления Storyboard теперь является контроллером навигации, который ни к чему не подключен. Я использовал различие setRootViewController:, потому что MainIdentifier - это UITabBarController. Тогда вот как выглядят мои строки:

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

Предложения / улучшения приветствуются!

Ответы:


25

Я предполагаю, что ваша раскадровка установлена ​​как «основная раскадровка» (ключ UIMainStoryboardFileв вашем Info.plist). В этом случае UIKit загрузит раскадровку и установит свой начальный контроллер представления в качестве корневого контроллера представления вашего окна перед отправкой application:didFinishLaunchingWithOptions:в ваш AppDelegate.

Я также предполагаю, что начальный контроллер представления в вашей раскадровке - это контроллер навигации, на который вы хотите поместить свой основной контроллер представления или контроллер представления входа в систему.

Вы можете запросить у своего окна контроллер корневого представления и отправить ему performSegueWithIdentifier:sender:сообщение:

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

1
Я реализовал ваши строки кода в своем приложении: didFinishLaunchingWithOptions: method. Отладка показывает, что rootViewController действительно является исходным контроллером навигации, однако переход не выполняется (отображается панель навигации, остальное - черный). Я должен сказать, что начальный контроллер навигации больше не имеет rootViewController, только 2 сегмента (StartLoginSegue и StartMainSegue).
— mmvie

3
Ага, у меня тоже не работает. Почему вы отметили его как "Отвеченный", если он вам не подходит?
— daidai

3
Я считаю, что это правильный ответ. Вам необходимо 1. иметь свойство окна [[self window] makeKeyAndVisible]в делегате вашего приложения и 2. вызывать application: didFinishLaunchingWithOptions:, прежде чем пытаться выполнить условные переходы. Предполагается, что UIApplicationMain () отправляет сообщение makeKeyAndVisible, но делает это только после того, как didFinish ... Параметры: Finishes. Дополнительные сведения см. В разделе «Координация усилий между контроллерами представления» в документации Apple.
— edelaney05

Это правильная идея, но не совсем работает. См. Мой ответ для рабочего решения.
— Matthew Frederick

@MatthewFrederick Ваше решение будет работать, если исходный контроллер является контроллером навигации, но не если это контроллер простого представления. Настоящий ответ - просто создать контроллер окна и корневого представления самостоятельно - действительно, это то, что Apple рекомендует в Руководстве по программированию контроллера представления. Подробнее см. Мой ответ ниже.
— followben

170

Я удивлен некоторыми предлагаемыми здесь решениями.

На самом деле нет необходимости в фиктивных контроллерах навигации в вашей раскадровке, скрытии представлений и запуске переходов на viewDidAppear: или любых других хаках.

Если в вашем plist-файле не настроена раскадровка, вы должны сами создать и окно, и корневой контроллер представления :

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

Если раскадровка будет настроена в PLIST в приложении, окно и корневой контроллер представления уже будет настройка приложение времени: didFinishLaunching: называюсь, и makeKeyAndVisible будет вызываться окном для вас.

В этом случае все еще проще:

- (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 Обращаем внимание - я просто скопировал имена переменных OP, но обновил свой ответ для ясности. Ура.
— followben

для случая раскадровки: если вы используете UINavigationViewcontroller в качестве корневого контроллера представления, вам нужно будет нажать следующий контроллер представления.
— Shirish Kumar

Для меня это более интуитивно понятный способ, нежели переход через сложный иерархический контроллер навигации. Love this
— Эллиот Яп

Привет @followben, в моем приложении у меня есть rootViewController в storyBoard, его tabBarController и все связанные VC с tabBar также разработаны в VC, так что теперь у меня есть случай, в котором я хочу показать пошаговое руководство по моему приложению. Итак теперь, когда мое приложение запускается впервые, я хочу сделать пошаговый VC в качестве корневого VC вместо tabBarcontroller, и когда мое пошаговое руководство закончится, я хочу сделать tabBarController в качестве rootViewController. Я не понимаю, как это сделать
— Ранджит

1
Что делать, если запрос к серверу асинхронный?
— Лиор Бург,

18

ЕСЛИ точка входа вашей раскадровки не является 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;
}

ЕСЛИ точка входа вашей раскадровки - это UINavigationControllerзамена:

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

с участием:

//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
Сработало хорошо. Просто комментарий, разве это не показывает "firstViewControllerIdentifier" только после того, как они вошли изначально? Так не следует ли это изменить? appHasLaunchedOnce ? secondViewControllerIdentifier : firstViewControllerIdentifier;
— ammianus

@ammianus ты прав. Их нужно поменять местами, и я отредактировал.
— Разван

9

В вашем методе AppDelegate application:didFinishLaunchingWithOptionsперед return YESстрокой добавьте:

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

Замените YourStartingViewControllerна имя вашего фактического первого класса контроллера представления (тот, который вы не хотите обязательно отображать) и YourSegueIdentifierфактическое имя перехода между этим начальным контроллером и тем, с которого вы действительно хотите начать (тот, который находится после перехода ).

Оберните этот код в ifусловное выражение, если вы не хотите, чтобы это всегда происходило.


6

Учитывая, что вы уже используете раскадровку, вы можете использовать ее, чтобы представить пользователю MyViewController, настраиваемый контроллер ( немного уваривая ответ followben ).

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

Строка, переданная в instantiateViewControllerWithIdentifier, относится к идентификатору раскадровки, который можно установить в построителе интерфейса:

введите описание изображения здесь

Просто оберните это логикой по мере необходимости.

Однако, если вы начинаете с UINavigationController, этот подход не даст вам элементов управления навигацией.

Чтобы «перескочить» от начальной точки контроллера навигации, настроенного с помощью конструктора интерфейсов, используйте следующий подход:

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

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

    return YES;
}

4

Почему бы не иметь экран входа в систему, который появляется первым, проверить, вошел ли уже пользователь, и сразу же перейти на следующий экран? Все в ViewDidLoad.


2
Это действительно работает, но моя цель - показать изображение запуска, пока приложение все еще ожидает ответа сервера (независимо от того, был ли вход в систему успешным или нет). Точно так же, как приложение Facebook ...
— mmvie

2
У вас всегда может быть ваш первый просмотр только UIImage, который использует то же изображение, что и ваш всплеск, и в фоновом режиме проверяет, вошли ли в систему, и отображает следующее представление.
— Даррен

3

Быстрая реализация того же:

Если вы используете в UINavigationControllerкачестве точки входа в раскадровке

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

Это решение, которое сработало на iOS7. Чтобы ускорить начальную загрузку и не выполнять ненужную загрузку, у меня есть полностью пустой UIViewcontroller с именем «DUMMY» в моем файле раскадровки. Тогда я могу использовать следующий код:

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

Я предлагаю создать новый MainViewController, который является корневым контроллером представления контроллера навигации. Для этого просто удерживайте control, затем перетащите соединение между Navigation Controller и MainViewController, выберите «Relationship - Root View Controller» из приглашения.

В MainViewController:

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

Не забудьте создать переходы между MainViewController с контроллерами представления Home и Login. Надеюсь это поможет. :)


0

Попробовав много разных методов, я смог решить эту проблему следующим образом:

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

Если вы не слишком далеко от Тейлора, возможно, вы захотите провести рефакторинг до чего-то более простого. Подробности см. В моем ответе :)
— followben
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.