After my rootViewController, there is a workflow that would go like this
viewController1 -> push viewController2 -> push viewController3 -> ***** viewController2
I would like to popToViewController3. However, I need to go to viewController1 first because viewController1 basically determines which instance of viewController2 to push. So I
// pseudo code
[popToViewController:vc1 animated:NO];
[self.navigationController pushViewController2 animated:YES];
I basically pop before pushing so I don't end up getting vc2, vc3, vc2, vc3, vc2 etc. But in doing this, since it animates the push of the last viewController, it gives the user the impression that a new viewController was pushed onto the stack, vs popping to the previous viewController. Is there a way to get around this ? If I animate the pop and not the last push, I do not get the correct viewController I want on top by the looks of it. Thanks.
can you use something like
[self.navigationController setViewControllers:[NSArray arrayWithObject:yourViewController]
animated:YES];
you can fill the array with the view controllers you want on the stack.
#interface UINavigationController (PushThenPop)
-(void)popToViewController:(UIViewController*)rootViewController
thenPushViewController:(UIViewController*)pushViewController;
#end
#implementation UINavigationController (PushThenPop)
-(void)popToViewController:(UIViewController*)rootViewController
thenPushViewController:(UIViewController*)pushViewController
{
NSMutableArray *viewControllers = self.viewControllers.mutableCopy;
// Pop viewcontrollers until we reach the rootViewController
while(viewControllers.count >= 1) {
id last = [viewControllers lastObject];
if (last == rootViewController) break;
[viewControllers removeLastObject];
}
// Bail out the array is empty. We expect the array to contain at least 1 element, the rootViewController.
if (!viewControllers.count) {
return;
}
// Push the new view controller
[viewControllers addObject:pushViewController];
self.viewControllers = viewControllers.copy;
}
#end
You can retrieve (viewControllers), modify, and "reinstall" (setViewControllers) the list of view controllers managed by the navigation controller, and the "reinstall" can (in theory) be animated. (However, I had some problems with animation in practice, though I don't recall the details.)
Related
Man I must say if it wasn't for this site, I would have no hair and I would've probably jumped off of a bridge right now. OK! My problem:
I'm using storyboard with xcode 4.4.
I have a 3 view controllers (well 5 really but the others i THINK are irrelevant). View controller A pushes to view controller B. View controller B has a segmented control on its nav bar with 2 segments and a back button.View contoller B loads with segment 1 selected. When user selects segment 0, view controller C is then instantiated. All well and good on the first push. You get pushed to B from A, and can then when you press back it pops to A. My problem is when I popToViewController to an array list containing view controller A, it pops, but it doesnt animate! I know what you're thinking, "Animate:YES dummy!" However, animated IS yes..HUH? Here's my code:
heres whats goin on in view controller B
-(IBAction)Previous:(id)sender{
[self.navigationController popViewControllerAnimated:YES];
}
- (IBAction)segmentSwitch:(id)sender;
{
if ([sender selectedSegmentIndex] == 0)
{
MapViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"Map"];
[self.navigationController pushViewController:controller animated:NO];
}
else if ([sender selectedSegmentIndex] == 1) {
ListViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"List"];
[self.navigationController pushViewController:controller animated:NO];
}
}
heres view controller C
-(IBAction)Previous:(id)sender
{
NSInteger index = -1;
ReportAppDelegate *appDelegate =
(ReportAppDelegate *)[[UIApplication sharedApplication]delegate];
MainOrangeTestViewController *orangeViewController = [appDelegate orangeViewController];
orangeViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Orange"];
NSArray* newVCarray = [[NSArray alloc] initWithObjects:orangeViewController, nil];
self.navigationController.viewControllers = newVCarray;
for(int i=0 ; i<[newVCarray count] ; i++)
{
if([[newVCarray objectAtIndex:i] isKindOfClass:NSClassFromString(#"MainOrangeTestViewController")])
{
index = i;
}
}
[self.navigationController popToViewController:[newVCarray objectAtIndex:index] animated:YES];
}
- (IBAction)segmentSwitch2:(id)sender; {
if ([sender selectedSegmentIndex] == 0)
{
MapViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"Map"];
[self.navigationController pushViewController:controller animated:NO];
}
else if ([sender selectedSegmentIndex] == 1)
{
ListViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"List"];
[self.navigationController pushViewController:controller animated:NO];
}
}
I know when you instantiate you are creating a new instance of that view controller as well as creating a new nav stack (or something like that). I'm thinking that might have something to do with it. Regardless, why on earth is it not animating the pop? I'm hoping to implement something in my current build, however if it requires a rebuild then so be it! Any help is greatly appreciated. I've definitely done my homework on this one however no solution.
Thanks to all in advance!
EDIT: I should add that the initial POP back to A from view controller B is animated. HOwever when you start to toggle the segmented controls AND when you toggle to view contrller C, going back to view controller A doesn't animate the pop. So initially when you load to B from A it pops back with successful animation. But after the toggle it pops back to A with no animation and the animated method is YES.
Are you just trying to push A->B->C and then pop back to A again, skipping B? If so, there's no need to modify the navigation controller's viewControllers property.
// in vcC.m ...
-(IBAction)Previous:(id)sender
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
Check out the different varieties of pop in the class reference. You can pop back one, or pop to the root, or pop to a particular VC that's in your history. The way to do that is to read the viewControllers stack (not write it), find the the VC you want to pop to, then use the popTo... method that's in your code.
But it sounds like popToRoot is what you'll need.
Still a little confused about your situation, but here's another tip: You can create history beforehand. Let's say you want to push from VC0 to VC2, and when popping, you want to pop from VC2 to either VC1 or to VC0. Totally doable:
// in VC0.m, animated push to VC2
// push to vc1 invisibly
[self.navigationController pushViewController:vc1 animated:NO];
// we've made history! now push visibly to vc2
[self.navigationController pushViewController:vc2 animated:YES];
To choose where to pop back...
// in VC2.m
if (/* user needs a regular pop back to VC1 */) {
[self.navigationController popViewControllerAnimated:YES];
} else // pop back two, to VC0
NSArray *viewControllers = [self.navigationController viewControllers];
// we're not assuming that vc0 is the root here, the top of the navigation stack
// is at index=count-1. self is at the top. a regular pop is just below == count-2.
// two pops is at count-3, and so on.
NSInteger backBackIndex = viewControllers.count - 3;
// in case we get here by some other pushing path, worst case pop is to the root
backBackIndex = MAX(backBackIndex, 0);
UIViewController *backBackVC = [viewControllers objectAtIndex:backBackIndex];
[self.navigationController popToViewController:backBackVC animated:YES];
}
I am using popToViewController method to pop to a view controller. What I was trying to achieve is to pop to an UITabBarController's specific index.
NSArray *viewArrays = [self.navigationController viewControllers];
So my view hierarchy is;
<LoginViewController: 0x6b75850>,
<UITabBarController: 0x6ba0b50>,
<RequestViewController: 0x684fe90>,
<ApplyViewController: 0x6845790>
Following code does pops to my UITabBarController, but since I was on the third view of my UITabBarController, it pops back to the third view
[[self navigationController] popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
What I want is to pop to the second view of my UITabBarController.
Edit (Answer)
As stated, we don't push or pop. So in order to gather your tabBarController you either use;
UITabBarController *myTabController = [self.navigationController.viewControllers objectAtIndex:indexOfYourTabBarControllerInYourNavigationController];
myTabController.selectedIndex = indexToGo;
or if you are on one of your tabBarController views;
self.tabBarController.selectedIndex = indexToGo;
In UITabBarController you dont pop and push, if you want to go to the second view you would set the selected index instead
Like so
yourTabController.selectedIndex = 1;
Or if the current view controller is a part of the tabBarController do
self.tabBarController.selectedIndex = 1;
Within my iPhone App I use a UITabBarController that holds four UINavigationController. Each UINavigationController represents a specific navigation within my app.
Normally if I want to push a new controller onto the stack of the current UINavigationController I just do something like:
// push another controller onto the current NavigationController Stack
MyController* controllerToPush = [[MyController alloc]init];
[self.navigationController pushViewController:controllerToPush animated:YES];
[controllerToPush release];
However there are a few places within my app, where the user is located within on navigationController and his action should change the selected Tab of the UITabBarController and push the controller there. I tried something like:
// Get a reference to my appDelegate
MyAppDelegate* appDelegate = [[UIApplication sharedApplication] delegate];
// Change the selected Index of my TabBarController
[[appDelegate tabBarController] setSelectedIndex:0];
// Get a reference to the NavigationController whose Index within the TabBarController is 0
UINavigationController* navigationController = [appDelegate firstNavigationController];
// Push the newly initiliazed controller onto the navigationController
MyController* controllerToPush = [[MyController alloc]init];
[navigationController pushViewController:controllerToPush animated:YES];
[controllerToPush release];
But this approach doesnt work. The selectedIndex of the TabBar chances correctly, but the controllerToPush has not been added to the navigationController-Stack. What am I doing wrong?
Try doing[appDelegate.firstnavigationcontroller pushViewController:controllerToPush animated:YES];
I want to push a view controller onto the stack, then pop the first one that pushed the new one.
-(void) someMethod {
MegaSuperAwesomeViewController *tempVC = [[MegaSuperAwesomeViewController alloc] init];
[self.navigationController pushViewController:tempVC animated:YES];
[tempVC release];
// pop this VC, how?
}
EDIT: turns out I can pop back 2 view controllers instead once finished with the new VC. Still not what I wanted exactly, but it works. The downside is I need to set a flag to indicate that the covered view is completed.
Here's a technique of popping back two view controllers, which has a similar problem of yours of the current view controller and its navigationController property going away as soon as you do the first pop:
// pop back 2 controllers on the stack to the setup screen
//
// 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 back 2 controllers to the setup screen
//
[navController popViewControllerAnimated:NO];
[navController popViewControllerAnimated:YES];
alternatively, you can directly "party" on the navigation controllers stack of view controllers:
setViewControllers:animated: Replaces
the view controllers currently managed
by the navigation controller with the
specified items.
(void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated Parameters
viewControllers The view controllers
to place in the stack. The
front-to-back order of the controllers
in this array represents the new
bottom-to-top order of the controllers
in the navigation stack. Thus, the
last item added to the array becomes
the top item of the navigation stack.
animated If YES, animate the pushing
or popping of the top view controller.
If NO, replace the view controllers
without any animations. Discussion You
can use this method to update or
replace the current view controller
stack without pushing or popping each
controller explicitly. In addition,
this method lets you update the set of
controllers without animating the
changes, which might be appropriate at
launch time when you want to return
the navigation controller to a
previous state.
If animations are enabled, this method
decides which type of transition to
perform based on whether the last item
in the items array is already in the
navigation stack. If the view
controller is currently in the stack,
but is not the topmost item, this
method uses a pop transition; if it is
the topmost item, no transition is
performed. If the view controller is
not on the stack, this method uses a
push transition. Only one transition
is performed, but when that transition
finishes, the entire contents of the
stack are replaced with the new view
controllers. For example, if
controllers A, B, and C are on the
stack and you set controllers D, A,
and B, this method uses a pop
transition and the resulting stack
contains the controllers D, A, and B.
Availability Available in iOS 3.0 and
later. Declared In
UINavigationController.h
So, to "disappear" the view controller directly under you on the navigation stack, in your view controller's viewDidLoad, you could do this:
NSMutableArray *VCs = [self.navigationController.viewControllers mutableCopy];
[VCs removeObjectAtIndex:[VCs count] - 2];
self.navigationController.viewControllers = VCs;
I had trouble figuring this out also so I wanted to share how I got this to work.
Let's say you have a stack of VCs VC1 being the root then you push VC2 and from VC2 you want to push VC3 but once pushed you don't want the user to go back to VC2 but rather to VC1 (the root). The way to do that is:
//push VC3 from VC2
[[self navigationController] pushViewController:VC3 animated:YES];
// now remove VC2 from the view controllers array so we will jump straight back to VC1
NSMutableArray *viewHeirarchy =[[NSMutableArray alloc] initWithArray:[self.navigationController viewControllers]];
[viewHeirarchy removeObject:self];
self.navigationController.viewControllers = viewHeirarchy;
Hope this helps someone else
Thanks Bogatyr about the tip on 'party on the viewcontroller array for the navcontroller'. I just replaced the entire stack with the one viewcontroller I want to change to, and then log out all the viewcontrollers in the stack to make sure its the only one! Worked great - thanks!
RatingsTableViewController *newViewController = [[RatingsTableViewController alloc] init];
NSMutableArray * newVCarray = [NSMutableArray arrayWithObjects:newViewController, nil];
self.navigationController.viewControllers = newVCarray;
[newViewController release];
NSMutableArray *allControllers = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers];
for (id object in allControllers) {
NSLog(#"name VC: %#", object);
}
[allControllers release];
-(void)popToSelf{
NSArray *array = [self.navigationController viewControllers];
for (int i = 0 ; i < array.count ; i++) {
UIViewController *currentVC = [array objectAtIndex:i];
if ([currentVC isKindOfClass:[YourViewControllerClass class]]) {
[self.navigationController popToViewController:[array objectAtIndex:i] animated:YES];
}
}
}
I'm writing a standard table view application with a number of views in the hierarchy. When I've clicked in 3-4 views, is there a way to get back to the top view? I tried loading it, but then I lose the hierarchy.
I know this command will bring me back 1 view, which is what the 'back' button does:
[self.navigationController popViewControllerAnimated:YES];
You can use popToRootViewControllerAnimated: or popToViewController:animated: methods.
To get the viewcontroller to which you need to jump, get a list of all viewcontrollers from the navcontroller in an array and then select the viewcontroller from this array.
i.e. if your hierarchy is svc->svc2->vc1->vc2->vc3->vc4 and you want to go back to vc1 from vc4, do this
NSArray *viewControllers = [[self navigationController] viewControllers];
UIViewController *controller = [viewControllers objectAtIndex:2];
[[self navigationController] popToViewController:controller animated:YES];