简体   繁体   中英

How to skip 2 views when using navigation controller

In Xcode 5.0.2 I've created an iPhone app (the complete app code is on GitHub , please run pod install once - for SDWebImage ), which displays a list of social networks (Facebook, Google+, etc.), then - in another view - an UIWebView displaying OAuth2 web page and then a third view with user avatar and details (first name, last name, etc.):

Xcode截图

(Here the fullscreen screenshot of the storyboard shown above)

在此输入图像描述

Before I display the user avatar and details in the last view, I save the user data to NSUserDefaults .

My question is: when the user starts the app the next time and there is user data in NSUserDefaults already - how can I skip the first 2 views and display (without any animation) the last view with those user details?

I've put the following code to the AppDelegate.m :

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *key = [defaults objectForKey:@"key"];
    User *user = [User loadForKey:key];

    if (user) {
        UINavigationController *nc = (UINavigationController*)self.window.rootViewController;
        // XXX how to load the last view here? XXX
    }

    return YES;
}

and can see, that the user data is being loaded okay, but I don't understand how to jump to the last view (esp. because most of the UI is defined in the storyboard)?

UPDATE: I have tried following Tommy Alexander's suggestion (thanks!) with the following code in AppDelegate.m (and also assigned Details StoryboardID to the last view):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *key = [defaults objectForKey:@"key"];
    User *user = [User loadForKey:key];

    if (user) {
        UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
        UIViewController *uvc = [storyboard instantiateViewControllerWithIdentifier:@"Details"];
        NSLog(@"XXX uvc=%@ user=%@", uvc, user);
        [self.window.rootViewController presentViewController:uvc animated:YES completion:nil];
    }

    return YES;
}

and while the last view (the UserViewController with user avatar and details) seems to be instantiated okay, I get the following warning and the app doesn't jump to the last view:

MyAuth[3917:70b] XXX uvc=<UserViewController: 0x8acbac0> user=
VK
59751265
Alexander
Farber
1945522
http://cs319319.vk.me/v319319265/b7df/TnyKeffL_mU.jpg
0
MyAuth[3917:70b] Warning: Attempt to present <UserViewController: 0x8acbac0> on <UINavigationController: 0x8bb3a30> whose view is not in the window hierarchy!

UPDATE 2: When I move the above code to the viewDidLoad of the 1st view (the MasterViewController.m , where the user can select a social network), then I get another warning:

MyAuth[1358:70b] XXX uvc=<UserViewController: 0x8a97430> user=
FB
597287941
Alexander
Farber
Bochum, Germany
http://graph.facebook.com/597287941/picture?type=large
0
MyAuth[1358:70b] Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x8a893e0>.

UPDATE 3: Thanks for the valuable answers! I've ended up using Leonty Deriglazov suggestion:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UIViewController *vc1 = [storyboard instantiateViewControllerWithIdentifier:@"Master"];
    //UIViewController *vc2 = [storyboard instantiateViewControllerWithIdentifier:@"Login"];
    UIViewController *vc3 = [storyboard instantiateViewControllerWithIdentifier:@"User"];

    User *user = [User load];
    NSArray *controllers = (user ? @[vc1, vc3] : @[vc1]);

    UINavigationController *nc = (UINavigationController *)self.window.rootViewController;
    [nc setViewControllers:controllers];

    return YES;
}

In a nutshell, the most straightforward solution is to use -[UINavigationController setViewControllers:animated:] .

You need to do the following to achieve what you want:

  1. assign storyboard IDs to all your controllers,
  2. instantiate all three view controller from the story board (this is lightweight as long as you don't do most of the initialisation work in controller's constructors)
  3. put them into an array in the same order you have them in storyboard,
  4. call -[UINavigationController setViewControllers:] with the array as the first argument.

That's it. The code in your -[AppDelegate application:didFinishLaunchingWithOptions:] might look like this (after checking your skipping condition of course):

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *vc1 = [storyboard instantiateViewControllerWithIdentifier:@"MyAuth"]; //if you assigned this ID is storyboard
UIViewController *vc2 = [storyboard instantiateViewControllerWithIdentifier:@"Login"];  //if you assigned this ID is storyboard
UIViewController *vc3 = [storyboard instantiateViewControllerWithIdentifier:@"Details"];
NSArray *controllers = @[vc1, vc2, vc3];
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
[navController setViewControllers:controllers];

If you paste just this to -[AppDelegate application:didFinishLaunchingWithOptions:] , you'll see that it works immediately.

Breaking view hierarchy will always gives weird behavior.

By using below mentioned approach user will not be able to notice that LoginViewController is presented and then we have pushed to Detail screen.

This approach always worked for me

In LoginViewController.m File

- (void)viewDidLoad
{

if (user) {
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UserViewController *userViewController = [storyboard instantiateViewControllerWithIdentifier:@"Details"];


[self.navigationController pushViewController:listViewVC animated:YES];

    }
}

In UserViewController.m File

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:YES];

if (user) {

    self.navigationItem.hidesBackButton = YES;

}

}

I think your problem starts with your initial Storyboard setup. I would use something like the below image.

I would make the User View controller my root view controller and present the login view controllers modally with or without animation.

Once the login succeeds it will be pretty convenient to dismiss the view controllers. And if the user logs out later on you'll just need to present the login view controllers again.

在此输入图像描述

I think you can do this :

if (hasUser) {
    MyFirstViewController *firstViewController = [myStoryboard instantiateViewControllerWithIdentifier:@"FirstViewControllerStoryboardId"];
    SecondViewController *secondViewController = [myStoryboard instantiateViewControllerWithIdentifier:@"SecondViewControllerStoryboardId"];

    [myNavigationController pushViewController:firstViewController animated:NO];
    [myNavigationController pushViewController:secondViewController animated:YES];
}

This way you will not have any animations and the navigation stack will be respected. Change the 'animated' boolean to play with the animation(s).

Hope it helps !

According to your screen workflow, you have a navigation controller and other view controllers are pushed into it.

So, the logic is simple: if a user is cached set details view controller as a root of your navigation view controller , else do nothing (storyboard's default will be used).

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

    if (hasUser) {
        UIViewController *vc = [nc.storyboard instantiateViewControllerWithIdentifier:@"DetailsViewController"];
        nc.viewControllers = @[vc];
    }

    return YES;
}

UPDATE

In case you need to go back to previous view controllers (eg from details to table), you can set the whole navigation hierarchy in navigation controller:

UINavigationController *nc = (UINavigationController*)self.window.rootViewController;
if (hasUser) {
    UIViewController *vc = [nc.storyboard instantiateViewControllerWithIdentifier:@"DetailsViewController"];
    [nc pushViewController:vc animated:NO];
}

Try giving your last UIViewController a StoryBoardID in the Attributes Inspector in IB. Then, once you determine if you have a user in NSUSerDefault:

-Get a reference to your storyboard:

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];

-Insatiate an instance of your last view controller (remember to import your last view contorllers .h file):

UIViewController *myController = [storyboard instantiateViewControllerWithIdentifier:@"StoryBoardID"];

-Present the VC:

[self presentViewController:myController animated:YES completion:nil]; 

Use this :

[self.presentingViewController dismissViewControllerAnimated:NO completion:^{
    [self.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}];

The best approach will be to use to UnwindSegue..

What are Unwind segues for and how do you use them?

UPDATE IF COMMENT IS NOT CLEAR

I don't know..

maybe try this

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UIViewController *uvc = [storyboard instantiateViewControllerWithIdentifier:@"Details"];
[self.window setRootViewController:uvc];

if not then make UINavigationController as rootViewController and make ivc as its rootViewController..

Hope that helps

What I do in one of my apps, is I have an additional segue from the rootViewController to the screen I want to skip to (in your example, from MyAuth to Details). Then, when I show the UINavigationController , I also set a variable in the rootViewController specifying if I want to skip to another screen. Finally, in the viewDidLoad: of the view controller I do a check:

// Check if we want to skip to another screen
if (self.skipToDetails == YES) {
    [self performSegueWithIdentifier:@"detailsView" sender:nil];
}

If you turn off animation for this segue in the storyboard file, the user won't see the first screen at all.

Also, you'll need to set hidesBackButton to YES in the Details screen.

Edit: The "unbalanced calls to begin/end" happens when you move to another VC (either push or modal) from the original ( rootViewController ) VC in viewDidLoad: . Moving the code to viewDidAppear: should help.

@Alexander please check screenshot you can manage the controller like this:

在此输入图像描述

And use following code in user view controller:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *key = [defaults objectForKey:@"key"];
User *user = [User loadForKey:key];

if (!user) {
 MasterViewController *loginController = [self.storyboard instantiateViewControllerWithIdentifier:@"MasterViewController"];
[self presentViewController:masterController animated:YES completion:nil];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *key = [defaults objectForKey:@"key"];
    User *user = [User loadForKey:key];

  UINavigationController *navcon = (UINavigationController *)self.window.rootViewController;

    if (hasUser) {
        UIViewController *viewcon = [navcon.storyboard instantiateViewControllerWithIdentifier:@"DetailsViewController"];
        [navcon pushViewController:viewcon animated:NO]; }   
}
else
   {
       UIViewController *viewcon = [navcon.storyboard instantiateViewControllerWithIdentifier:@"UserViewController"];
       [navcon pushViewController:viewcon animated:NO];}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM