So I'm trying to pop a view controller off the stack when an error occurs, but it seems like it's popping too much off in one go. The navigation bar up the top loses its title and buttons, but the old table view data remains visible. I have no idea what's going on...
The basic set up is:
Tab View template
Navigation controller
View controller (Loaded from the xib)
View controller (Pushed, what I want to pop)
Here's the code:
NSLog(#"%#", [[self navigationController] viewControllers]);
[[self navigationController] popViewControllerAnimated:NO];
NSLog(#"%#", [[self navigationController] viewControllers]);
The resulting NSLog's show:
2009-09-22 19:57:14.115 App[34707:550b] (
<MyViewController: 0xd38a70>,
<MyViewController: 0xd36b50>
)
2009-09-22 19:57:14.115 App[34707:550b] (null)
Anyone have experience with this?
I am seeing some odd UINavigationController stack behavior with just using
[self.navigationController popViewControllerAnimated:YES];
inside of a delegate called out of a tableView:didSelectRowAtIndexPath: call.
The views don't work right, and a UINavigationController provided "back" operation doesn't correctly pop the view of this delegate, going back another level.
I found that if I used
[self.navigationController popToViewController:self animated:YES];
instead, that suddenly everything worked fine. This is in an application with ARC turned on.
So, I can only guess that there is some reference housekeeping that doesn't happen correctly unless you tell it to pop back to a specific view controller when you will pop a view controller that will immediately become "unreferenced" by that pop.
I fixed it. The code that was popping the view was getting called in viewDidLoad. This meant it was getting popped before the view had actually animated in completely.
I moved that code to viewDidAppear and now it works as advertised.
[[self navigationController] popViewControllerAnimated:NO];
//here you pop **self** from navigation controller. And now
[self navigationController] == nil;
// And
[nil viewControllers] == nil
Try to do this:
UINavigationController *nc = [self navigationController];
NSLog(#"%#", [nc viewControllers]);
[nc popViewControllerAnimated:NO];
NSLog(#"%#", [nc viewControllers]);
What is wrong with?
[self.navigationController popViewControllerAnimated:NO];
Also, you may want to check how you push the ViewController onto the stack. Something doesn't sound right.
Related
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
iphone - “Pushing the same view controller instance more than once is not supported” exception
I am properly pushing test2ViewController from 1 as follows,
self.controller2 = [[test2ViewController alloc] initWithNibName:#"test2ViewController" bundle:nil anUser:self.idUser];
[[self navigationController] pushViewController:self.controller2 animated:NO];
[self.controller2 release];
from 2 to 1 I pop it after initialize 1 again (necessary to initialize).
self.controller1 = [[test1ViewController alloc] initWithNibName:#"test1ViewController" bundle:nil anUser:self.idUser];
[[self navigationController] popToRootViewControllerAnimated:NO];
[self.controller1 release];
and problem appers when trying to push again 2 from 1, app crashes with an error,
Pushing the same view controller instance more than once is not supported
what am doing wrong? thank you.
Well first off you are creating another instance of test2ViewController so you will go to a different instance each time you change view.
What you should do:
if(!test2ViewController)
secondView = [[test2ViewController alloc] init...];
[self navigationController pushViewController:secondView animated:NO];
and to return, simply:
[self.navigationController popViewControllerAnimated:NO];
PoppingtoRoot causes you to pop to the very first view controller that used the pushViewController method.
Judging by the code you posted you only push one view controller (controller2) to your navigation controller.
popToRootViewControllerAnimated: will remove all view controllers from the stack except the root view controller (which seems to be controller2 in your case). So basically it does nothing.
Then you try to push the same view controller2 again and it fails, because as the error message said, that's not allowed.
You do not need to reinitialize viewController1 again; if you are pushing the viewController2 from 1, than you only have to invoke
[self.navigationController popToRootViewControllerAnimated:NO];
because viewController1 is already in the stack. What this method will do is to remove all viewControllers on the stack except the first one and move back to it.
If viewController1 is not the rootView controller you should use
[self.navigationController popViewControllerAnimated:NO];
which will pop only the last pushed viewcontroller on the stack and reveal the one beneath it.
How can i go back 2 or 3 views back without using navigation controller? That is in my app, there is a main menu view. i want to reach that menu from all the other pages (from multiple views). How can this be implemented? with
[self dismissModalViewControllerAnimated:YES]; this cannot be implemented i suppose.
Anybody please help me..
I found the solution.
Of course you can find the solution in the most obvious place so reading from the UIViewController reference for the dismissModalViewControllerAnimated method ...
If you present several modal view controllers in succession, and thus build a stack of modal view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
so it's enough to call the dismissModalViewControllerAnimated on the target View. I used the following code:
[[[[[self parentViewController] parentViewController] parentViewController] parentViewController] dismissModalViewControllerAnimated:YES];
to go back to my home.
Copied from here
"Going 2 views back without navigation controller"
Hmmm, I'm not sure if this misses the point, but the easiest way to do this is to use the popToRootViewControllerAnimated in the Navigation Controller:
[self.navigationController popToRootViewControllerAnimated:TRUE];
So, supposing you had a series of three screens in a Navigation Controller, and on the third screen you wanted the "Back" button to take you back to the initial screen.
On the third screen, you would add this code:
-(void)viewDidLoad
{
[super viewDidLoad];
// change the back button and add an event handler
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(handleBack:)];
}
-(void)handleBack:(id)sender
{
NSLog(#"About to go back to the first screen..");
[self.navigationController popToRootViewControllerAnimated:TRUE];
}
Do you want to go "two or three VIEWS back"? Use removeFromSuperview? Or are you talking about ViewControllers?
Are you using Storyboard?
If yes do:
- (void)showModalAssistantViewController
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"AssistantStoryboard" bundle:nil];
AssistantRootViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"AssistantNavigationController"];
[viewController setModalPresentationStyle:UIModalPresentationFullScreen];
[viewController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self.navigationController presentModalViewController:viewController animated:YES];
//... or pushToViewController ... whatever, you get the point.
}
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
I have uinavigation controller with a view controller named VC1,now i have a button in VC1 that when i click on him i go to view controller VC2,this is how i bring VC2 to screen :
VC2 *tmp = [[VC2 alloc]init];
[[self navigationController] pushViewController:tmp animated:YES];
[tmp release];
now when i click in VC2 on the back button of the navigation to return to VC1 its work but i put in VC2 viewDidDisappear and viewWillDisappear methods,and when i click on the back button this function won't called, any one have any idea?
You may call the view[..] methods manually from the UINavigationControllerDelegate callbacks, however the easiest way to ensure that the methods are called by the super implementation of the UINavigationController is just to call them manually once when the UINavigationController is allocated.
See my answer here: iPhone viewWillAppear not firing
So immediately after you have called navController = [[UINavigationController alloc] init.., make sure to call
[navController viewWillAppear:NO];
[navController viewDidAppear:NO];
[navController viewWillDisappear:NO];
[navController viewDidDisappear:NO];
This should ensure events are correctly forwarded to each view controller in the future.
If you want to call the viewWillDisappear/viewDidDisappear methods, your view controller has to do that manually before popping itself off the nav stack. Have a look at this
,you might get an idea how to do it.
My navigationcontroller becomes nil after my "[self.navigationController popToViewController: [self.navigationController.viewControllers objectAtIndex:0] animated:YES];"
this is my scene:
InsertViewController - > [self.navigationController pushViewController:choiceViewController animated:YES];
ChoiceViewController -> [self.navigationController pushViewController:choiceDetailViewController animated:YES];
ChoiceDetailViewController ->
InsertViewController *insertViewController = [self.navigationController.viewControllers objectAtIndex:0] ;
UINavigationController *secondaryNavigationCtrl = [[UINavigationController alloc] initWithRootViewController:insertViewController];
secondaryNavigationCtrl.navigationBar.barStyle = UIBarStyleBlackOpaque;
[self presentModalViewController:secondaryNavigationCtrl animated:YES];
[secondaryNavigationCtrl release];
[
When "ok" button pressed( self.navigationItem.leftBarButtonItem) in InsertView that just poped up, then it goes back to ChoiceDetailViewController and i do a
[code][self.navigationController dismissModalViewControllerAnimated:YES]; [/code]
After that i do a
[self.navigationController popToViewController: [self.navigationController.viewControllers objectAtIndex:0] animated:YES];
Which go back tot the InsertViewController, and when i do the cycle again i see my navigationcontroller is nil...
Any idea what I am doing wrong?
Thanks in advance.
I'm not sure I've understand what you are trying to do but, when you go back to ChoicheDetailViewController i think you should do a [self.navigationController popViewController:...] instead of [self.navigationController dismissModalViewControllerAnimated:YES] because ChoicheDetailViewController got pushed on the stack in a non modal way in the first place. So with your code you are actually dismissing the whole navigationController.
Not sure if this fixes your problem but I had this problem because the class that implemented the code that popped the view controller was actually getting popped. This caused my self.navigationController to be nil because it itself was getting removed. I moved the code to a class that was not getting popped and it did not get set to nil.
Before the stack looked like this, one of the ViewControllers gets popped self.navigationController is nil.
PopableViewController
functionThatCallsPopToViewController
PopableViewController
functionThatCallsPopToViewController
RootViewController
After
PopableViewController
PopableViewController
RootViewController
functionThatCallsPopToViewController
Since RootViewController is not popped self.navigationController did not get set to nil. The only tricky thing is, now you need to keep a reference to the RootViewController in your other viewControllers.
I have a tabBarController with two tabs, first of which contains an instance of NavigatorController. The navigatorController is initiated with a custom viewController "peersViewController" that list all the network peers on a tableView. Upon selecting a peer, an instance of "FilesListViewController" (which list files in the c:\ directory) is pushed into the navigationController stack.
In this filesListViewController I have a button to let it navigate to say documents directory. To do this I'd wired the interface to call a gotoDirectory:(NSString*)path method in the rootViewController:
- (void)gotoDirectory:(NSString*)path {
[[self navigationController] popToRootViewControllerAnimated:YES];
NSArray *files = [self getFilesFromPeerAtPath:path];
FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
[[self navigationController] pushViewController:filesVC animated:YES];
[filesVC release];
}
However, when I press that button, the navigationController did pop my view to the root view controller, but then the FilesListViewController that I instantiated did not appear. From the log, I know that the custom initWithFiles method was indeed called and network stuffs did happen to get the file names.
Something else is screwy about this. I tried clicking on the second tab and then click back to the first tab, and huala! the file names I needed are there. It looks like the data and the filesListViewController was indeed pushed into the navigatorController stack, but the display was not refreshed but stuck at the screen of rootViewController (peersViewController).
Am I doing anything wrong?
--Ben.
-- Edited like 15 minutes after posting the question. I'd found a workaround, but it bothers me that pop and then push doesn't work.
- (void)gotoDirectory:(NSString*)path {
PeersListViewController *rootViewController = (PeersListViewController*)[[[self navigationController] viewControllers] objectAtIndex:0];
[[self navigationController] setViewControllers:[NSArray arrayWithObject:rootViewController]];
FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
[[self navigationController] pushViewController:filesVC animated:YES];
[filesVC release];
}
It doesn't seem like the navigationController should be circumvented this way, and I'd probably have to release all the viewControllers that were in the original stack. This does however work on the iphone 3.0 simulator.
If I'm using this code though, how should the memory release be handled? should I get the original NSArray of viewcontrollers and release everything?
The problem and solution to this issue is actually extremely simple.
Calling [self.navigationController popToRootViewControllerAnimated:YES] sets self.navigationController to nil. When you subsequently call [self.navigationController pushViewController:someOtherViewController] you are effectively sending a message to nil, which does nothing.
To workaround, simply set up a local reference to the navigationController and use that instead:
UINavigationController * navigationController = self.navigationController;
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:someOtherViewController animated:YES];
As stated by Jason, the popToRootViewController must be performed without animation for this to work correctly.
Thanks go to jpimbert on the Apple forums for pointing this out.
I got a very similar problem (but without using tab).
I got three viewController : main(root), form and result.
when the UINavigationController stack is
"main -> result"
on a btnClick I do a popToRootViewControllerAnimated then a push of the formViewCtrl.
in order to have
"main -> form"
the navbar title and back button label are correct and the formViewCtrl's event are called.
BUT, I still see the main view.
Here is my "solution"
After doing some test, I found out that without the animation to go to the rootViwCtrl this work fine. So I only use the animation to push viewCtrl.
iPhone 3.0, problem found on device & simulator.
If i got something new, i will update/comment my post.
I see that this question about popping to the root and then pushing a new ViewController is pretty prevalent, and this post is viewed a lot, so I wanted to add my bit to help other new guys out, especially those using Xcode 4 and a storyboard.
In Xcode 4, you have a storyboard. Let's say you have these view controllers: HomeViewController, FirstPageViewController, SecondPageViewController. Make sure to click each of them and name their identifiers by going to the Utilities pane->Attributes Inspector. We'll say they're named Home, First, and Second.
You are Home, then you go to First, then you want to be able to go to Second and be able to press the back button to go back to Home. To do this, you want to change your code in FirstPageViewController.
To expand on the example, make a button in FirstPageViewController in the storyboard. Ctrl-drag that button into FirstPageViewController.m. In there, the following code will achieve the desired outcome:
// Remember to add #import "SecondPageViewController.h" at the top
SecondPageViewController *secondView = [self.storyboard instantiateViewContorllerWithIdentifier:#"Second"];
UINavigationController *navigationController = self.navigationController;
NSArray *array = [navigationController viewControllers];
// [array objectAtIndex:0] is the root view controller
NSArray *viewControllersStack = [NSArray arrayWithObjects:[array objectAtIndex:0], secondView, nil];
[navigationController setViewControllers:viewControllersStack animated:YES];
Basically, you're grabbing the view controllers, arranging them in a stack in the order you want, and then having the navigation controller use that stack for navigation. It's an alternative to pushing and popping.
I found a workaround but I cannot explain why it is working:
1. First push the needed controller.
2. Then pop to the one you want to.
This is totally illogical, but it works for my case.
Just to make things clear, I'm using it in the following scenario:
First Screen -> Goes to Loading Screen -> Second Screen
When I'm on the Second Screen, I don't want to have the Loading Screen in the stack and when click back I should go to the First Screen.
Regards,
Vesko Kolev
You can actually keep the "Go back" animation, followed by the "Go forward" animation by basically delaying the push animation till after the pop animation is complete. Here is an example:
(Note: I have an NSString variable called "transitionTo" in my appDelegate that's initially set to #"")...First, set that variable to an NSString you can detect for later. Then, pop the controller to give you a nice screen transition back to the root:
appDelegate.transitionTo = #"Another";
[detailNavigationController popToRootViewControllerAnimated:YES];
Then inside the rootviewcontroller's class, use the viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate =(AppDelegate*) [UIApplication sharedApplication].delegate;
if([appDelegate.transitionTo isEqualToString:#"Another"])
{
[self transitionToAnotherView];
appDelegate.transitionTo = #"";
}
}
-(void)transitionToAnotherView
{
// Create and push new view controller here
AnotherViewController *controller = [[AnotherViewController alloc] init];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Home" style:UIBarButtonItemStyleBordered target:nil action:nil];
[self.navigationItem setBackBarButtonItem:backButton];
[[self navigationController] pushViewController:controller animated:YES];
}
So basically, pop to the root...when the transition finishes at "viewDidAppear"...then push the next view. I happened to keep a variable to tell you which view you wish to transition to (with #"" meaning not to do a transition in the case that I want to stay on this screen).
Nick Street's answer works great if you want to popToRootViewController and subsequently push another VC.
VC1 -> VC2 -> VC3: hit the back button from VC3 => VC2, then VC1, here OK
However, when VC1 pushes VC2, which in turn pushes VC3, then going back to VC1 directly from VC3 does not work as wished:
I've implemented in VC3's -(void)viewWillDisappear:(BOOL)animated:
-(void)viewWillDisappear:(BOOL)animated{
...
[self.navigationController popToRootViewControllerAnimated:YES];
}
I also tried to implement it in the "back button", same result: upon hitting the back button from VC3 to go back to VC1: it breaks. The actual VC is VC1, but the navigation bar is still VC2. Playing with other combinations, I get VC1's navBar on VC2. Total mess.
Loda mentioned something about timing. I think that's the main issue here. I've tried a few things, so maybe I'm missing out something here, but this is what worked for me, at last:
In VC3:
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// notify VC2
[[NSNotificationCenter defaultCenter] postNotificationName:backFromV3 object:self];
}
In VC2:
-(void)viewDidLoad {
...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backFromV3)
name:#"BackFromV3"
object:nil];
}
-(void)backFromV3{
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(backToRootViewController)
userInfo:nil
repeats:NO];
}
-(void)backToVC1 {
self.navigationItem.rightBarButtonItem = nil;
[self.navigationController popToRootViewControllerAnimated:YES];
}
Of course, do the necessary cleaning.
The timer is critical here. If 0, it breaks. 0.5 seems to be alright.
That works perfectly for me. A little heavy, but I have not been able to find anything that does the trick.