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의 초기보기 컨트롤러는 이제 아무것도 연결되지 않은 탐색 컨트롤러입니다. MainIdentifier가 UITabBarController이기 때문에 setRootViewController : 구분을 사용했습니다. 그러면 내 라인은 다음과 같습니다.

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

스토리 보드가 "기본 스토리 보드"( UIMainStoryboardFileInfo.plist의 키)로 설정되어 있다고 가정합니다 . 이 경우 UIKit은 스토리 보드를로드하고 application:didFinishLaunchingWithOptions:AppDelegate로 보내기 전에 초기보기 컨트롤러를 창의 루트보기 컨트롤러 로 설정합니다.

또한 스토리 보드의 초기보기 컨트롤러가 기본 또는 로그인보기 컨트롤러를 푸시하려는 탐색 컨트롤러라고 가정합니다.

창에 루트 뷰 컨트롤러를 요청하고 performSegueWithIdentifier:sender:메시지를 보낼 수 있습니다.

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

1
내 application : didFinishLaunchingWithOptions : 메소드에서 코드 줄을 구현했습니다. 디버깅은 rootViewController가 실제로 초기 탐색 컨트롤러임을 보여 주지만 segue는 수행되지 않습니다 (탐색 막대가 표시되고 나머지는 검은 색임). 초기 탐색 컨트롤러에는 더 이상 rootViewController가없고 2 개의 segue (StartLoginSegue 및 StartMainSegue) 만 있다고 말해야합니다.
mmvie dec

3
네, 나에게도 일하지 않습니다. 작동하지 않는데 왜 답변 됨으로 표시 했습니까?
다이 다이

3
나는 이것이 정답이라고 믿습니다. 1. 앱 델리게이트에 window 속성이 있어야하고 2. [[self window] makeKeyAndVisible]조건부 segue를 시도하고 수행하기 전에 application : didFinishLaunchingWithOptions :를 호출 해야합니다. UIApplicationMain ()은 makeKeyAndVisible에 메시지를 보내는 것으로 가정하지만 didFinish ... Options : 완료 후에 만 ​​그렇게합니다. 자세한 내용은 Apple 문서에서 "View Controller 간 노력 조정"을 참조하십시오.
edelaney05

이것은 올바른 생각이지만 제대로 작동하지 않습니다. 작동하는 솔루션에 대한 내 대답을 참조하십시오.
Matthew Frederick

@MatthewFrederick 초기 컨트롤러가 탐색 컨트롤러 인 경우 솔루션이 작동하지만 일반보기 컨트롤러 인 경우에는 작동하지 않습니다. 진짜 대답은 창과 루트 뷰 컨트롤러를 직접 만드는 것입니다. 실제로 이것은 뷰 컨트롤러 프로그래밍 가이드에서 Apple이 권장하는 것입니다. 자세한 내용은 아래 내 대답을 참조하십시오.
followben

170

여기에서 제안 된 몇 가지 솔루션에 놀랐습니다.

스토리 보드에 더미 탐색 컨트롤러가 필요하지 않고 viewDidAppear에서 뷰를 숨기고 segue를 실행하거나 다른 해킹을 할 필요가 없습니다.

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에 구성되어있는 경우 application : 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 Spot on-방금 OP의 변수 이름을 복사했지만 명확성을 위해 답변을 업데이트했습니다. 건배.
followben

스토리 보드의 경우 : 루트 뷰 컨트롤러로 UINavigationViewcontroller를 사용하는 경우 다음 뷰 컨트롤러를 푸시해야합니다.
Shirish Kumar 2013

이것은 복잡한 계층 구조 탐색 컨트롤러를 통과하는 것보다 더 직관적 인 방법입니다. 이 사랑
엘리엇 얍

안녕하세요 @followben, 내 앱에서는 storyBoard에 rootViewController가 있고 tabBarController이고 tabBar와 관련된 모든 VC도 VC로 설계되었으므로 이제 내 앱의 둘러보기를 표시하려는 케이스가 있습니다. 이제 내 앱이 처음 시작될 때 tabBarcontroller 대신 루트 VC로 walkthrough VC를 만들고 싶고 내 연습이 완료되면 tabBarController를 rootViewController로 만들고 싶습니다. 그것을 수행하는 방법 이해
Ranjit

1
서버에 대한 요청이 비동기이면 어떻게됩니까?
Lior Burg

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 당신이 맞습니다. 그것들을 뒤집어서 편집해야합니다.
Razvan

9

AppDelegate의 application:didFinishLaunchingWithOptions메서드에서 return YES줄 앞에 다음을 추가합니다.

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

교체 YourStartingViewController실제 첫 번째 뷰 컨트롤러 클래스 (당신이 반드시 표시하지 않는 한)의 이름과 YourSegueIdentifierSEGUE의 실제 이름 사이의 컨트롤러와 실제로합니다 (SEGUE 후 하나에 시작하려는 일을 시작 ).

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에 전달 된 문자열은 인터페이스 빌더에서 설정할 수있는 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;
}

4

먼저 나타나는 로그인 화면이 나오지 않는 이유는 사용자가 이미 로그인되어 있는지 확인하고 바로 다음 화면을 푸시하는 것입니다. 모두 ViewDidLoad에 있습니다.


2
이것은 실제로 작동하지만 내 목표는 앱이 여전히 서버 응답을 기다리는 한 (로그인 성공 여부에 관계없이) 시작 이미지를 표시하는 것입니다. 그냥 페이스 북의 애플 리케이션과 같은 ...
mmvie

2
첫 번째보기는 스플래시와 동일한 이미지를 사용하는 UIImage 만 가질 수 있으며 백그라운드에서 로그인되어 있는지 확인하고 다음보기를 표시 할 수 있습니다.
Darren

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에서 작동하는 솔루션입니다. 초기로드 속도를 높이고 불필요한로드를 수행하지 않기 위해 스토리 보드 파일에 "DUMMY"라는 완전히 비어있는 UIViewcontroller가 있습니다. 그런 다음 다음 코드를 사용할 수 있습니다.

- (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를 만드는 것이 좋습니다. 그렇게하려면 컨트롤을 누른 상태에서 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 뷰 컨트롤러 사이에 segues를 생성하는 것을 잊지 마십시오. 도움이 되었기를 바랍니다. :)


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

Taylor와 너무 멀지 않다면 더 간단한 것으로 리팩토링하는 것이 좋습니다. 자세한 내용은 내 대답을 참조하십시오. :)
followben
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.