28

In Short : How can I PushViewController from Presented ViewController ?

In Brief :

I have MainViewController, In which I have one button on click of button, I am presenting a view called LoginViewController.

On this page (LoginViewController), I again have button, on click of that, I try to push my view controller(called HomeViewController) it doesn't pushes.

Here is my code snippet,

MainViewController.m

- (IBAction)LoginClicked:(id)sender {
    LoginViewController *vc = [[LoginViewController alloc] init];
    [self presentViewController:vc animated:YES completion:nil];
}

LoginViewController.m

- (IBAction)buttonActionMethodOnLoginView:(id)sender{
     NSLog(@"viewControllers %@",APPDELEGATE.nav.viewControllers);
     //LoginViewController is not in this array
     HomeViewController *obj = [[HomeViewController alloc] init];
     [self.navigationController pushViewController:obj animated:YES];
}

But it did not works for me. Also, I printed a stack of view controllers before pushed, but it doesn't have LoginViewController. So, without adding LoginViewController into a stack of view controllers, How can I pushed to HomeViewController from LoginViewController ?

When I getBack from HomeViewController, then LoginViewController should get opened..

Is it possible using doing this single NavigationController?

Note:- Here, I have just taken an example using Login, Home and Main ViewController. But I want that into Other Screens.

Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
  • Does `LoginViewController` implement `UINavigationViewController`? You can try to push `HomeViewController` with `self presentViewController` like LoginViewController. – Leo Jan 04 '16 at 12:41
  • Yes, I can `present` that `HomeViewController`. But, I want to `push` that `HomeViewController` from `LoginViewController`.. – Meet Doshi Jan 04 '16 at 12:47
  • 2
    Your LoginViewController must have its NavigationController to push from LoginView Controller. – technerd Jan 04 '16 at 12:49
  • @technerd : Yes, But how can I done that ? – Meet Doshi Jan 04 '16 at 12:53
  • @MeetDoshi, have you checked my answer? Did you get some help? – Hemang Jan 21 '16 at 07:52
  • @MeetDoshi The recent update in your question changes it totally and none of the answers is matching the "new" question. – Nat Jan 25 '16 at 09:37
  • I know that.. But I want that.. And Thanks for answering to solving my this issue.. Please help me according this.. And my recent update on this question works into accepted answer.. – Meet Doshi Jan 25 '16 at 09:39

11 Answers11

29

hi when you are Presenting you Login view controller Just present a navigationController like:

LoginVC *loginVCObj =[[LoginVC alloc]initWithNibName:@"LoginVC" bundle:nil];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:loginVCObj];
[self presentViewController:nav animated:YES completion:nil];

Now your PresentedViewController is An navigtioncontroller now you can simply push to your Home VC

  HomeViewController *obj = [[HomeViewController alloc] init];
 [self.navigationController pushViewController:obj animated:YES];

Hope it will helpful for you

Shubham bairagi
  • 943
  • 7
  • 25
  • Using this code, I can able to `pushed` view controller. But `LoginVC` doesn't `dismissed`. So when I dismiss `LoginVC`, then it get back to `HomeViewController`. And when I `pop` this, It get back to `MainViewController`. So this doesn't solved my issue. – Meet Doshi Jan 04 '16 at 13:37
  • Let me explain acc. to my code. when u r on mainVC and Presenting 'UINavigationController' nav. now you are on different NavigationController Not on previous one if you wanna push to HomeVC from LoginVC. your mainVC not be in this Navigation. – Shubham bairagi Jan 04 '16 at 13:43
  • Okay. But how can done this with one `NavigationController`? – Meet Doshi Jan 04 '16 at 13:49
  • 3
    @Shubhambairagi What did you put into the completion block? OMG – Luca D'Alberti Jan 04 '16 at 13:59
12

LoginViewController should not be pushed to navigation controller stack. Let me describe below "why".

Our MainViewController should be on the stack - you always want to go back there.

// AppDelegate.m (only if you don't use storyboards, if you do - you don't need to copy this part of code)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // create the window
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window setBackgroundColor:[UIColor whiteColor]];
    [self.window makeKeyAndVisible];

    // set view controllers
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:[[MainViewController alloc] init]];
    [self.window setRootViewController:navigationController];
}

On specific action show LoginViewController. You don't want the user to be able to tap back and go to MainViewController. Later, you won't want user to go back to LoginViewController. Because of this, you need to present it as modal:

// inside `MainViewController.m`
- (IBAction)myCoolActionToShowLogin:(id)sender {
    [self presentViewController:[[LoginViewController alloc] init] animated:YES completion:nil];
}

Now we can see LoginViewController. When user completes the login, dismiss it and present HomeViewController:

// inside `LoginViewController.m`
- (IBAction)myAwesomeActionToShowHome:(id)sender {
    UINavigationController *navigationController = (UINavigationController *)[UIApplication.sharedApplication.keyWindow rootViewController];
    [navigationController pushViewController:[[HomeViewController alloc] init] animated:YES];
    [self dismissViewControllerAnimated:YES completion:nil];
}

NOTES:

As you may notice, myAwesomeActionToShowHome: expects you have navigation controller as your rootViewController. This is working, but should be nicer - you should check if that navigation is in fact navigation controller instead of casting it. Or you may create a delegate or block to push new one. This is the fastest, easiest working solution, which should be improved later.

You really should read: Apple Developer -> "View Controller Programming" documentation, as these are the core fundamentals you should know to develop & design UX correctly.

Here is the working demo sample.

Nat
  • 12,032
  • 9
  • 56
  • 103
  • As you didn't mention, I assumed you don't use Storyboards. This affect only AppDelegate. If you use Storyboards, you have the entry in AppDelegate "completed" by Storyboard and you don't have to use that code. Anyway, two methods left are correct for both Storyboard and Code solutions. – Nat Jan 21 '16 at 17:02
  • This doesn't work me.. Using this, when I trying to push a view from presented view, at that time a view controller pushed but never showed, when I dismissed my presented view, at that time my pushed view showed. So, My issue was, when I push a view controller from presented view, at that time I want to show that pushed view controller without dismissing my presented view.. – Meet Doshi Jan 25 '16 at 06:03
  • @MeetDoshi I've added one line inside the code of `myAwesomeActionToShowHome`, where the current controller is being dismissed. – Nat Jan 25 '16 at 08:58
  • Ok thanks.. here you dismissed that view when we push.. But now when I get back from HomeViewController, then I want to open my presented view (LoginViewController), then how can I do this..? – Meet Doshi Jan 25 '16 at 09:03
  • 1
    @MeetDoshi I've created a working demo for you :) Please check out the link: https://github.com/natalia-osa/SOFPushPopDemo – Nat Jan 25 '16 at 09:05
  • Usually there shouldn't be a possibility to go back to the LoginViewController. In most cases, when user logs in, he never wants to go back to that screen. Common practice is to add navigationBarButtonItem to sign out. But there is no need to display LoginViewController once user logged in. Probably your user experience will suffer upon this decision. – Nat Jan 25 '16 at 09:08
  • Thanks for the reply and Demo.. Yes I know.. Here, I have just take one example for LoginViewController. I want that into other screens. Your demo works same.. But I wants that, When I getBack from CViewController, then BViewController should get opened.. – Meet Doshi Jan 25 '16 at 09:19
  • 1
    @MeetDoshi If you want to get back to that controller, it shouldn't be presented via `presentViewController:animated:` but via `self.navigationController pushNavigationController:`. The navigation controller stack will handle going back and forward. `present..` should be used only when you want to show it and not "save in history". You seem to have some holes in knowledge about how to work with `UINavigationController`, `UITabBarController` and managing view controllers. Please read: https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/index.html. – Nat Jan 25 '16 at 09:33
  • Ok thanks.. and please update your answer with SOFPushPopDemo code link.. Thats really nice demo. – Meet Doshi Jan 25 '16 at 09:43
  • 1
    @MeetDoshi Glad I was able to help and thanks for accepting. Provided the link to the demo in the answer ;) – Nat Jan 25 '16 at 10:11
10

You can't push from a presented view controller. I suggest, you should maintain your navigation hierarchy.

For that, from your MainViewController, you should present LoginViewController and you should pass navigation controller for the MainViewController.

- (IBAction)openLogin:(id)sender {
    LoginViewController *loginVC = (LoginViewController *) [self.storyboard instantiateViewControllerWithIdentifier:@"login"];
    [loginVC setReferencedNavigation:self.navigationController];
    [self presentViewController:loginVC animated:YES completion:nil];
}

Then inside LoginViewController, you should push to HomeViewController like this,

LoginViewController.h

@interface LoginViewController : UIViewController {
    UINavigationController *refNavigationController;
}
- (void) setReferencedNavigation:(UINavigationController *)refNavCon;

LoginViewController.m

- (void) setReferencedNavigation:(UINavigationController *)refNavCon {
    refNavigationController = refNavCon;
}

- (IBAction)openHome:(id)sender {
    [self dismissViewControllerAnimated:YES completion:^{
        UIViewController *homeVC = [self.storyboard instantiateViewControllerWithIdentifier:@"home"];
        [refNavigationController pushViewController:homeVC animated:YES];
    }];
}

By doing this, it will be look like, you're pushing from LoginViewController but in reality you're pushing from MainViewController.

You can customize this approach to maintain animation and UI for this flow.

enter image description here

Hemang
  • 26,840
  • 19
  • 119
  • 186
  • Its working for storyboard.. But what about xib?? I want to implement this into xib project. – Meet Doshi Jan 21 '16 at 08:30
  • Why? What's the issue with XIBs? You only need to change the way you'll create object of your view controller. – Hemang Jan 21 '16 at 09:56
  • Yes I tried that.. But manually I can not able add my viewcontroller in to Navigation Controller stack.. – Meet Doshi Jan 21 '16 at 10:25
  • 1
    You can check [this](http://stackoverflow.com/a/22981748/1603234), [this](http://www.idev101.com/learn/navigation_controllers.html) and [this](http://stackoverflow.com/a/20782023/1603234). These links show you how to create navigation controller and attached a view controller programmatically. Let me know if you still need more help. – Hemang Jan 21 '16 at 10:45
  • I already checked that. And I tried this.. `NSArray *stack = [NSArray arrayWithObjects:[self.navigationController.viewControllers objectAtIndex:0], myViewController, nil];` `[self.navigationController setViewControllers:stack animated:NO];` using [this](http://stackoverflow.com/q/26844821/3908884) . – Meet Doshi Jan 21 '16 at 10:58
  • 1
    I'm not sure why you're doing this. It seems to be improper. Instead, your MainViewController should be inside `UINavigationController`. Thus, you don't need to reset anything while presenting `LoginViewController`. E.g. `LoginViewController *controller = [[LoginViewController alloc] initWithNibName:@"LoginView" bundle:[NSBundle mainBundle]];` – Hemang Jan 21 '16 at 11:53
  • Because I can not able to used `setReferencedNavigation` method at my end. I don't know why I can not able to used that.. Thats why I tried that.. – Meet Doshi Jan 21 '16 at 11:56
8

1) present a navigation controller with itsroot view controller` set as view controller .

- (IBAction)LoginClicked:(id)sender 
{
    LoginViewController *loginViewController = [LoginViewController alloc] init];
    UINavigationController *navController = [UINavigationController alloc] initWithRootViewController:loginViewController];
    [self presentViewController:navController animated:YES completion:nil];
}

- (IBAction)buttonActionMethodOnLoginView:(id)sender
{
    HomeViewController *obj = [[HomeViewController alloc] init];
    [self.navigationController pushViewController:obj animated:YES];
}

Hope it will work for you.

P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
Rohit Pradhan
  • 3,867
  • 1
  • 21
  • 29
6

The problem is that LoginViewController has no navigation controller. Then you give it one.

MainViewController.m

Create a UINavigationController, put LoginViewController in to the stack and present the UINavigationController.

- (IBAction)LoginClicked:(id)sender {
    LoginViewController *vc = [[LoginViewController alloc] init];
    UINavigationController = nav = [[UINavigationController alloc] init];
    nav.viewControllers = @[vc];
    [self presentViewController:nav animated:YES completion:nil];
}

LoginViewController.m

- (IBAction)buttonActionMethodOnLoginView:(id)sender{
    HomeViewController *obj = [[HomeViewController alloc] init];
    [self.navigationController pushViewController:obj animated:YES];
}

Dismiss

Call dismissViewControllerAnimated in your MainViewController.

ukim
  • 2,395
  • 15
  • 22
6

For Swift 3.0

Present your view controller as a new rootViewController

let navController = UINavigationController.init(rootViewController: self.storyboard!.instantiateViewController(withIdentifier: "SignInViewController"))
self.present(navController, animated: true, completion: {})

Now push your view controller from presented view controller

self.show(self.storyboard!.instantiateViewController(withIdentifier: "SignUpViewController"), sender: self)
emraz
  • 1,563
  • 20
  • 28
4

Create a UINavigationController instance

[[UINavigationController alloc] initWithRootViewController:[[LoginViewController alloc] init]]

Present that navigationController and then push whatever VC you want to.

Hunaid Hassan
  • 732
  • 5
  • 13
4

MainViewController.m

 - (IBAction)LoginClicked:(id)sender {

     LoginViewController *vc = [[LoginViewController alloc] init];

     UINavigationController *loginNav = [[UINavigationController alloc] initWithRootViewController:vc]; 

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

 }

LoginViewController.m

- (IBAction)buttonActionMethodOnLoginView:(id)sender{
     NSLog(@"viewControllers %@",APPDELEGATE.nav.viewControllers);
     //LoginViewController is not in this array
     HomeViewController *obj = [[HomeViewController alloc] init];
     [self.navigationController pushViewController:obj animated:YES];
}
darshan
  • 1,115
  • 1
  • 14
  • 29
4

You have to Push from your firstView (MainViewController), but you can use animation same as PresentView and DismissView. Use following code for this :-

For Push (on MainViewController)

LoginViewController *VC = [[LoginViewController alloc]init];
CATransition* transition = [CATransition animation];
transition.duration = 0.3f;
transition.type = kCATransitionMoveIn;
transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition
                                            forKey:kCATransition];
[[[UINavigationController alloc] initWithRootViewController:VC] pushViewController:VC animated:NO];
//[self.navigationController pushViewController:VC animated:NO];

For Pop (on LoginViewController)

CATransition* transition = [CATransition animation];
transition.duration = 0.3f;
transition.type = kCATransitionReveal;
transition.subtype = kCATransitionFromBottom;
[self.navigationController.view.layer addAnimation:transition
                                            forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];

Using this code, you can get animation same as Present-Dismiss ViewControllers. Refer this answer for more details.

And after that, you can use your code for Pushing LoginViewController to HomeViewController

Hope, this is what you're looking for. Any concern get back to me. :)

Community
  • 1
  • 1
Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
4

This is very simple code for present view controller and push view controller.

- (IBAction)LoginClicked:(id)sender {
        LoginViewController *objLogicVC = [LoginViewController alloc] init];
        UINavigationController *navPresent = [UINavigationController alloc] initWithRootViewController:objLogicVC];
        [self presentViewController:navPresent animated:YES completion:nil];
}

- (IBAction)buttonActionMethodOnLoginView:(id)sender{
        HomeViewController *objHomeVC = [[HomeViewController alloc] init];
        [self.navigationController pushViewController:objHomeVC animated:YES];
}
Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
Bera Bhavin
  • 673
  • 5
  • 10
  • You didn't get my point. Please Read my Question once again.. My main issue was, *When I getBack from HomeViewController, then LoginViewController should get opened..* – Meet Doshi Feb 11 '16 at 09:49
3

simply put this code in objective c on button action

UIViewController *yourViewControllerName = [self.storyboard instantiateViewControllerWithIdentifier:@"yourViewControllerName "];

[[self navigationController] pushViewController:yourViewControllerName  animated:YES];
Yaseen Ahmad
  • 1,807
  • 5
  • 25
  • 43
iTALIYA
  • 972
  • 8
  • 14