I have created a module in which i have 5 classes in this i need to perform a task like
vc1->vc2->vc3
now from vc3 i used to push vc4 from the reference of a base class.
base class is a class on which vc1 controller's is added as a subview.
and now i need to pop from vc4 to vc3.
NSInteger index = 0;
for (UIViewController *view in self.navigationController.viewControllers) {
if([view.nibName isEqualToString:#"YourViewController"])//put any `XIB name` where u want to navigate
break;
index = index + 1;
}
//[[self navigationController] pushViewController:[[self.navigationController viewControllers] objectAtIndex:index] animated:YES];
[[self navigationController] popToViewController:[[self.navigationController viewControllers] objectAtIndex:index] animated:YES];
NSArray *array = [self.navigationController viewControllers];
[self.navigationController popToViewController:[array objectAtIndex:2] animated:YES];
Get the index of the view controller you want to pop to.
Please refer to these links :-
Can i pop to Specific ViewController?
pop a specific view controller
I am making a small app with the following view hierarchy with UINavigationController:
Login -> Options -> three different views
The problem is that I would like to navigate between the last 3 views in the following manner:
1<->2
1<->3
2<->3
i.e. to be able to switch to any view from any other view, which reminds UITabViewController functionality. So, it is not hierarchical, it is any-to-any graph. To switch between views I will use buttons in the navigation bar.
The easiest way for me is to subclass UINavigationController, add properties that correspond to my views and implement methods for switching between these views (using pushViewController and popToRootViewController). These methods will be called from the views for switching (navigating).
However the reference says that UINavigationController is not intended for subclassing.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html
What do you recommend me to do?
I'll keep the UINavigationController but instead of using the usual pushViewController:, switch views like this:
NSMutableArray *viewControllers = [self.navigationController.viewControllers mutableCopy];
// from here you can modify the order of controllers as much as you want
[viewControllers addObject:nextViewController];
[viewControllers removeObject:self];
[self.navigationController setViewControllers:viewControllers animated:YES];
If you don't want how the animation turns out, you can set animated:NO and either enclose setViewControllers: in an [UIView animate...] block, or add your own custom CAAnimation to the navigation controller's layer.
Use the below code to add a view controller to a navigation controller,
Navigating From first -> second
SecondViewController *secondView = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
NSMutableArray *navigationarray = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers];
[navigationarray removeAllObjects];
[navigationarray addObject:secondView];
self.navigationController.viewControllers = navigationarray;
Navigating From first -> third
ThirdViewController *thirdView = [[ThirdViewController alloc] initWithNibName:#"ThirdViewController" bundle:nil];
NSMutableArray *navigationarray = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers];
[navigationarray removeAllObjects];
[navigationarray addObject:thirdView];
self.navigationController.viewControllers = navigationarray;
The above code will removes all the viewControllers from the Navigation Array and places a fresh View Controller
If u want to go to a particular view controller, then use the below code...
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES]
Change the index to ur view controller in the stack.
I want to go back to the first view controller. So, I used [self.navigationController popToRootViewControllerAnimated:NO] from the third view. But, it goes back to just the second view. Do I have to use popToViewController: animated: instead?
I pushed the third view like this:
[self.view addSubview:secondController.view]; // from the first view
[self.navigationController pushViewController:thirdController animated:YES]; // from the second view
Remove the second view, before using [self.navigationController popToRootViewControllerAnimated:NO]:
UIView *v = [self.navigationController.viewControllers objectAtIndex:1];
[v removeFromSuperview];
EDIT:
I am doing like this and works ok (I use this on my third view on the stack):
NSMutableArray *allControllers = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers];
[allControllers removeObjectAtIndex:1];
[self.navigationController setViewControllers:allControllers animated:NO];
[allControllers release];
[self.navigationController popViewControllerAnimated:YES];
It looks like your navigationController was initiated when pushing the thirdController. Your secondController was not 'pushed' by the navigationController, it was added as a subview, which is quite different. So, when you push the thirdController from the secondController, it thinks your rootController is the secondController.
You have two options here:
Change the way you are presenting the secondController to actually
have the navigationController push it, or
Remove the secondController from view before the thirdController is presented.
You may be able to popToViewController, as you mentioned...I'm not positive if that will work, but it's possible.
I normally use UIViews to make my apps - but this one I am using a navigationcontroller. I am pushing a view to the top where I want to add items to an array. However, I cannot access the main navigation controller methods etc. Here's the set up
1) AppDelegate adds navigation controller
[window addSubview:navigationController.view];
2) In RootViewController I push a new UIView
AddNewViewController *addNewViewController = [[AddNewViewController alloc] init];
[self presentModalViewController: addNewViewController animated:YES];
3) From the AddNewViewController I then want to access the main RootViewController - but cannot seem to access anything. All the methods etc are declared as I've done it before (using UIViews). This code cannot find anything in the RootViewController.
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.navigationController myFunction];
I have myFunction in the RootViewController.h file. I have used this method before, but never with a navigation controller. I guess it's something to do with hte stack - but I cannot find out what I've done wrong!
Help is much appreciated!!
* UPDATE *
I have now used
AddNewViewController *addNewViewController = [[AddNewViewController alloc] init];
[self.navigationController pushViewController:addNewViewController animated:YES];
to push the view controller. In my code, I am trying to access the tableview to reload it with the following code
[self.navigationController.mainTableView reloadData];
but the mainTableView is not accessible. I have declared and sync'd it but still cannot see it. I also tried to loop through and use the element[0] of the stack (as below) but didn't get anywhere with that either!
NSArray *controllers = [[NSArray alloc] initWithObjects:self.navigationController.viewControllers, nil];
UIViewController *tmpController = [[UIViewController alloc] init];
tmpController = [controllers objectAtIndex:0];
First of all,
[self presentModalViewController: addNewViewController animated:YES];
does something different than
[self.navigationController pushViewController:addNewViewController animated:YES];
The names of those methods should speak for themselves.
And appDelegate.navigationController is not your rootViewController. It is the UINavigationController. You can get an NSArray of viewControllers attached to the navigationcontroller with self.navigationController.viewControllers. Item at index 0 should be your rootViewController.
If you push the viewController on the navigationControllers stack there is no need to use the appdelegate. You can access the navigationController with self.navigationController.
I have an application where I need to remove one view from the stack of a UINavigationController and replace it with another. The situation is that the first view creates an editable item and then replaces itself with an editor for the item. When I do the obvious solution within the first view:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];
I get very strange behavior. Usually the editor view will appear, but if I try to use the back button on the nav bar I get extra screens, some blank, and some just screwed up. The title becomes random too. It is like the nav stack is completely hosed.
What would be a better approach to this problem?
Thanks,
Matt
I've discovered you don't need to manually mess with the viewControllers property at all. Basically there are 2 tricky things about this.
self.navigationController will return nil if self is not currently on the navigation controller's stack. So save it to a local variable before you lose access to it.
You must retain (and properly release) self or the object who owns the method you are in will be deallocated, causing strangeness.
Once you do that prep, then just pop and push as normal. This code will instantly replace the top controller with another.
// locally store the navigation controller since
// self.navigationController will be nil once we are popped
UINavigationController *navController = self.navigationController;
// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];
// Pop this controller and replace with another
[navController popViewControllerAnimated:NO];
[navController pushViewController:someViewController animated:NO];
In that last line if you change the animated to YES, then the new screen will actually animate in and the controller you just popped will animate out. Looks pretty nice!
The following approach seems nicer to me, and also works well with ARC:
UIViewController *newVC = [[UIViewController alloc] init];
// Replace the current view controller
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
[viewControllers removeLastObject];
[viewControllers addObject:newVC];
[[self navigationController] setViewControllers:viewControllers animated:YES];
From experience, you're going to have to fiddle with the UINavigationController's viewControllers property directly. Something like this should work:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
[[self retain] autorelease];
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
self.navigationController.viewControllers = controllers;
[self.navigationController pushViewController:mevc animated: YES];
Note: I changed the retain/release to a retain/autorelease as that's just generally more robust - if an exception occurs between the retain/release you'll leak self, but autorelease takes care of that.
After much effort (and tweaking the code from Kevin), I finally figured out how to do this in the view controller that is being popped from the stack. The problem that I was having was that self.navigationController was returning nil after I removed the last object from the controllers array. I think it was due to this line in the documentation for
UIViewController on the instance method navigationController
"Only returns a navigation controller if the view controller is in its stack."
I think that once the current view controller is removed from the stack, its navigationController method will return nil.
Here is the adjusted code that works:
UINavigationController *navController = self.navigationController;
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
navController.viewControllers = controllers;
[navController pushViewController:mevc animated: YES];
Thanks, this was exactly what I needed. I also put this in an animation to get the page curl:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
UINavigationController *navController = self.navigationController;
[[self retain] autorelease];
[UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration: 0.7];
[UIView setAnimationTransition:<#UIViewAnimationTransitionCurlDown#> forView:navController.view cache:NO];
[navController popViewControllerAnimated:NO];
[navController pushViewController:mevc animated:NO];
[UIView commitAnimations];
0.6 duration is fast, good for 3GS and newer, 0.8 is still a bit too fast for 3G..
Johan
If you want to show any other view controller by popToRootViewController then you need to do following:
UIViewController *newVC = [[WelcomeScreenVC alloc] initWithNibName:#"WelcomeScreenVC" bundle:[NSBundle mainBundle]];
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
[viewControllers removeAllObjects];
[viewControllers addObject:newVC];
[[self navigationController] setViewControllers:viewControllers animated:NO];
Now, all your previous stack will be removed and new stack will be created with your required rootViewController.
I had to do a similar thing recently and based my solution on Michaels answer. In my case I had to remove two View Controllers from the Navigation Stack and then add a new View Controller on. Calling [controllers removeLastObject]; twice, worked fine in my case.
UINavigationController *navController = self.navigationController;
// retain ourselves so that the controller will still exist once it's popped off
[[self retain] autorelease];
searchViewController = [[SearchViewController alloc] init];
NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease];
[controllers removeLastObject];
// In my case I want to go up two, then push one..
[controllers removeLastObject];
navController.viewControllers = controllers;
NSLog(#"controllers: %#",controllers);
controllers = nil;
[navController pushViewController:searchViewController animated: NO];
This UINavigationController instance method might work...
Pops view controllers until the specified view controller is the top view controller and then updates the display.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
Here is another approach that doesn't require directly messing with the viewControllers array. Check if the controller has been pop'd yet, if so push it.
TasksViewController *taskViewController = [[TasksViewController alloc] initWithNibName:nil bundle:nil];
if ([navigationController.viewControllers indexOfObject:taskViewController] == NSNotFound)
{
[navigationController pushViewController:taskViewController animated:animated];
}
else
{
[navigationController popToViewController:taskViewController animated:animated];
}
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
for(int i=0;i<controllers.count;i++){
[controllers removeLastObject];
}
self.navigationController.viewControllers = controllers;
My favorite way to do it is with a category on UINavigationController. The following should work:
UINavigationController+Helpers.h
#import
#interface UINavigationController (Helpers)
- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller;
#end
UINavigationController+Helpers.m
#import "UINavigationController+Helpers.h"
#implementation UINavigationController (Helpers)
- (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller {
UIViewController* topController = self.viewControllers.lastObject;
[[topController retain] autorelease];
UIViewController* poppedViewController = [self popViewControllerAnimated:NO];
[self pushViewController:controller animated:NO];
return poppedViewController;
}
#end
Then from your view controller, you can replace the top view with a new by like this:
[self.navigationController replaceTopViewControllerWithViewController: newController];
You can check with navigation view controllers array which you give you all view controllers that you have added in navigation stack. By using that array you can back navigate to specific view controller.
For monotouch / xamarin IOS:
inside UISplitViewController class;
UINavigationController mainNav = this._navController;
//List<UIViewController> controllers = mainNav.ViewControllers.ToList();
mainNav.ViewControllers = new UIViewController[] { };
mainNav.PushViewController(detail, true);//to have the animation
Alternatively,
You can use category to avoid self.navigationController to be nil after popViewControllerAnimated
just pop and push, it's easy to understand, don't need to access viewControllers....
// UINavigationController+Helper.h
#interface UINavigationController (Helper)
- (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated;
#end
// UINavigationController+Helper.m
#implementation UINavigationController (Helper)
- (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIViewController *v =[self popViewControllerAnimated:NO];
[self pushViewController:viewController animated:animated];
return v;
}
#end
In your ViewController
// #import "UINavigationController+Helper.h"
// invoke in your code
UIViewController *v= [[MyNewViewController alloc] init];
[self.navigationController popThenPushViewController:v animated:YES];
RELEASE_SAFELY(v);
Not exactly the answer but might be of help in some scenarios (mine for example):
If you need to pop viewcontroller C and go to B (out of stack) instead of A (the one bellow C), it's possible to push B before C, and have all 3 on the stack. By keeping the B push invisible, and by choosing whether to pop only C or C and B altogether, you can achieve the same effect.
initial problem
A -> C (I want to pop C and show B, out of stack)
possible solution
A -> B (pushed invisible) -> C (when I pop C, I choose to show B or also pop it)
I use this solution to keep the animation.
[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];