Условно начать в разных местах раскадровки из 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;
}
Followben
источник
@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. Надеюсь это поможет. :)

thanhbinh84
источник
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;
}
AddisDev
источник
Если вы не слишком далеко от Тейлора, возможно, вы захотите провести рефакторинг до чего-то более простого. Подробности см. В моем ответе :)
followben