Условно начать в разных местах раскадровки из AppDelegate
У меня есть раскадровка с рабочим контроллером входа в систему и основного представления, последний является контроллером представления, к которому пользователь перемещается при успешном входе в систему. Моя цель - немедленно показать контроллер основного представления, если аутентификация (сохраненная в цепочке для ключей), прошла успешно, и показать контроллер представления входа в систему, если аутентификация не удалась. По сути, я хочу сделать это в моем 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
}
Я знаю о методе executeSegueWithIdentifier: но этот метод является методом экземпляра 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;
}
Предложения / улучшения приветствуются!
10 ответов
Я предполагаю, что ваша раскадровка установлена как "основная раскадровка" (ключ UIMainStoryboardFile
в вашем Info.plist). В этом случае UIKit загрузит раскадровку и установит свой начальный контроллер представления в качестве корневого контроллера представления вашего окна, прежде чем он отправит application:didFinishLaunchingWithOptions:
в ваш AppDelegate.
Я также предполагаю, что начальный контроллер представления в вашей раскадровке - это контроллер навигации, на который вы хотите вставить свой основной или входной контроллер представления.
Вы можете запросить у своего окна его корневой контроллер вида и отправить performSegueWithIdentifier:sender:
сообщение к нему:
NSString *segueId = success ? @"pushMain" : @"pushLogin";
[self.window.rootViewController performSegueWithIdentifier:segueId sender:self];
Я удивлен некоторыми из предлагаемых здесь решений.
В вашей раскадровке нет нужды использовать фиктивные контроллеры навигации, скрывать представления и запускать переходы в 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;
}
Если раскадровка настроена в списке приложений, контроллер окна и корневого представления уже будет настроен к тому моменту, когда приложение: 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;
}
Если точка входа вашей раскадровки не 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];
В вашем AppDelegate's application:didFinishLaunchingWithOptions
метод, перед return YES
строка, добавьте:
UINavigationController *navigationController = (UINavigationController*) self.window.rootViewController;
YourStartingViewController *yourStartingViewController = [[navigationController viewControllers] objectAtIndex:0];
[yourStartingViewController performSegueWithIdentifier:@"YourSegueIdentifier" sender:self];
замещать YourStartingViewController
с именем вашего фактического первого класса контроллера представления (тот, который вы не хотите обязательно показывать) и YourSegueIdentifier
с фактическим именем перехода между этим стартовым контроллером и тем, с которого вы хотите фактически начать (тот, что после перехода).
Оберните этот код в if
условно, если вы не всегда хотите, чтобы это произошло.
Учитывая, что вы уже используете раскадровку, вы можете использовать ее, чтобы представить пользователю 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, ссылается на Storyboard ID, который можно установить в конструкторе интерфейса:
Просто оберните это в логику по мере необходимости.
Однако, если вы начинаете с UINavigationController, этот подход не предоставит вам элементы управления навигацией.
Чтобы "прыгнуть вперед" от начальной точки контроллера навигации, настроенного через конструктор интерфейса, используйте этот подход:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UINavigationController *navigation = (UINavigationController *) self.window.rootViewController;
[navigation.visibleViewController performSegueWithIdentifier:@"my-named-segue" sender:nil];
return YES;
}
Почему бы не иметь экран входа в систему, который появляется первым, проверить, вошел ли пользователь в систему, и сразу же нажать следующий экран? Все в ViewDidLoad.
Быстрая реализация того же:
Если вы используете 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)
}
Это решение, которое работало на iOS7. Чтобы ускорить начальную загрузку и не делать ненужной загрузки, в моем файле раскадровки есть полностью пустой контроллер UIView, называемый "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;
}
Попробовав много разных методов, я смог решить эту проблему следующим образом:
-(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;
}
Я предлагаю создать новый MainViewController, который является Root View Controller для Navigation Controller. Для этого просто удерживайте элемент управления, затем перетащите соединение между Navigation Controller и MainViewController, выберите "Отношение - Root View Controller" из приглашения.
В MainViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
if (isLoggedIn) {
[self performSegueWithIdentifier:@"HomeSegue" sender:nil];
} else {
[self performSegueWithIdentifier:@"LoginSegue" sender:nil];
}
}
Не забудьте создавать переходы между MainViewController с контроллерами Home и Login. Надеюсь это поможет.:)