Changing root view controller after iOS app has loaded.
Asked Answered
C

4

11

In order to show my login screen when the app loads, and not after the user logs in, I have decided to add an auth object in NSUserDefaults when the user logs in successfully. When the app is launched that auth parameter is checked, and the view controller is set accordingly (if the user is auth it'll show a feed, if not it'll show a login screen) In the latter case, I have the app delegate reset the root view controller to the feed after the user has logged in. Is this bad practice or is there a better way of doing this?

In the app delegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary     *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    IIViewDeckController* deckController = [self generateControllerStack];
    self.rightController = deckController.rightController;
    self.centerController = deckController.centerController;

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    if([[defaults objectForKey:@"auth"] isEqualToNumber:[NSNumber numberWithInt:1]]){
        self.window.rootViewController = deckController;
    }else{
        UIStoryboard *sb = [UIStoryboard storyboardWithName:@"MainStoryboard"
                                                 bundle:nil];
        UIViewController* vc = [sb instantiateViewControllerWithIdentifier:@"loginViewController"];
        self.window.rootViewController = vc;
    }
    [self.window makeKeyAndVisible];
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque animated:NO];
    return YES;
}

- (void) setRoots
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    IIViewDeckController* deckController = [self generateControllerStack];
    self.rightController = deckController.rightController;
    self.centerController = deckController.centerController;
    self.window.rootViewController = deckController;
    [self.window makeKeyAndVisible];
}

In the login view controller:

- (IBAction)loginClick:(id)sender {
    if([_emailField.text length]>0&&[_passField.text length]>0){
        NSString *user = _emailField.text;
        NSString *pass = _passField.text;
        [[API sharedInstance] login:user andPass:pass onCompletion:^(NSDictionary *json){
            NSLog(@"%@", json);
            if(![json objectForKey:@"error"]){
                [API sharedInstance].authorized = 1;
                NSNumber *auth = [NSNumber numberWithInt:1];
                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                [defaults setObject:auth forKey:@"auth"];
                [defaults synchronize];

                captureYouAppDelegate *app = [[UIApplication sharedApplication] delegate];
                [app setRoots]; 
            }else{
                [API sharedInstance].authorized = 0;
            }
        }];
    }else{
        if([_emailField.text length]<1){
            [_emailField becomeFirstResponder];
        }else{
            [_passField becomeFirstResponder];
        }
    }
}

I'm wondering if there is a better or easier way than doing that. Thank you!

Crooks answered 14/8, 2013 at 8:4 Comment(2)
why don't you add a simple UINavigationController as rootViewController, and you just don't push the login screen into the navigation stack if the user already logged in. it would be much easier and much more elegant than your current solution is.Quadrennium
@Quadrennium If tab bar is also needed after logging in, adding a UINavigationController wouldn't be an option, right?Selfpity
F
6

Just to clarify. I had reset UIWindow's rootViewController before without any problem but when attempting to do so while allowing device rotation I ran into some issues — Rotation just stopped working.

I found the following directly from Apple's docs while trying to debug. The link below has a number of guidelines about working with UIWindow. These are all related to device rotation but still good to know.

Short answer, use a root controller and add child view controllers. You can then swap out the child VCs with no problem.

• You have added your view controller's UIView property to UIWindow as a subview.

Developers are discouraged from adding the view property of any view controller as a subview of UIWindow. Your application's root view controller should be assigned to the app window's rootViewController property either in Interface Builder, or at runtime before returning from application:didFinishLaunchingWithOptions:. If you need to display content from more than one view controller simultaneously, you should define your own container view controller and use it as the root view controller. See Creating Custom Container View Controllers.

Check this technical Q&A for more details.

Firn answered 5/12, 2014 at 15:58 Comment(0)
K
4

I don't think reset the window.rootViewController is a bad practice. However, there is no need to recreate a window.
If you don't want to use the previous view controller, just replace the window's rootViewController with the new view controller. If you do want to switch back to your previous view controller, use -presentViewController: animated: completion: to present your view controller may be a better alternative.

Kowalski answered 14/8, 2013 at 8:18 Comment(7)
Simply replacing it doesn't allow me to set the IIViewDeckController properties.Crooks
I agree with it, changing the rootViewController during runtime is a very poor code-pattern.Quadrennium
@Quadrennium Could you please explain why it's a very poor code-pattern? thx!Selfpity
@Kowalski Do you have any suggestion about how to release the previous rootViewController if I never need it? thx!Selfpity
@GoldThumb, literally there is no such a situation which demands the developer to change the rootViewController during runtime, literally everything can be done without touching the UIWindow. on other hand, regarding it had been set during the launch, and according to the application lifecycle's logic the application should not go back to almost the beginning of its own lifecycle. however, it can be done freely, but such procedure indicates that the view layer (and the navigation 'tree') has been designed poorly.Quadrennium
Anyone know anything about why device rotation is broken if you just set the rootVC? I've replaced the root before, works great, but never tried while allowing rotation until now.Firn
It's a poor pattern because 1) as mentioned before the view controller is not really dealloced 2) if it's not really dealloced a lot of things still happen with that View Controller including notification recieving and thus, some random methods being called while you're not really aware of that. 3) depending on how you do that, but generally you'll not have the usual good UX of transitioning between the screens as users got used to [ with animations and swift entering into another app context/idea].Masticatory
A
0

There is no need to alloc window again you can directly set this

window.rootViewController = yourVC;
Andreandrea answered 14/8, 2013 at 8:35 Comment(0)
H
0

Step By Step i am showing the Good Practice to use of rootviewcontroller with the help of UINavigationController

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    // Override point for customization after application launch.

    self.splash = [[SplashViewController alloc] initWithNibName: @"SplashViewController" bundle: nil];  
    self.window.rootViewController = self.splash;  
    [self.window makeKeyAndVisible];  
    DLog(@"finished initializing .....");  
    [self setRootViewController];  

    return YES;  
}



- (void) setRootViewController  
{  
    DLog(@"setRootViewController");  
    if (self.sessionManager.isLoggedIn)  
    {  
            [self navigateToHomeController];  

    } else {  
            [self navigateToLoginController];  
      }  
}


- (void) navigateToHomeController  
{  
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"kStoryBoard" bundle: nil];  
    UINavigationController* homeNavigationController =  [storyboard instantiateViewControllerWithIdentifier:@"kHomeNavController"];  
    NSArray* controllers = [homeNavigationController viewControllers];  
    if([controllers lastObject])  
    {  
        if ([[controllers objectAtIndex:0] isKindOfClass:[HomeViewController class]]) {  
            HomeViewController* homeController = (HomeViewController*) [controllers objectAtIndex:0];  
            self.window.rootViewController = [[OLNavigationViewController alloc] initWithRootViewController: homeController]; 
        }  
    }  
}



- (void) navigateToLoginController  
{  
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"kStoryBoard" bundle: nil];  
    UINavigationController* loginNavigationController = [storyboard instantiateViewControllerWithIdentifier:@"kLoginNavController"];  
    if ([loginNavigationController isKindOfClass:[OLLoginViewController class]]) {  
        OLLoginViewController* loginController = (OLLoginViewController*) loginNavigationController;  
        loginController.shouldHideToolBar = YES;  
        self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController: loginController];  
    }
}
Hydrothermal answered 7/7, 2016 at 5:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.