I need a little help on a problem with navigation controllers.
I have a navigationController with 4 ViewControllers pushed. The last vc I push presents a further ViewController modally. The modal ViewController presents an ActionSheet. Depending on the user's answer I either dismiss the modal ViewController only or I want to go back to the root ViewController.
In the ViewController presented modally I have:
- (void) dismissGameReport
{
[[self delegate] GameReportModalWillBeDismissed:modalToPopToRoot];
}
In the last ViewController pushed onto the navigationController stack I have:
- (void)GameReportModalWillBeDismissed: (BOOL)popToRoot;
{
if (popToRoot)
{
[self.navigationController popToRootViewControllerAnimated:NO];
}
else
{
[self dismissModalViewControllerAnimated:YES];
}
}
Dismissing the modal view controller works fine.
However,
[self.navigationController popToRootViewControllerAnimated:NO];
does not cause the root ViewController to display its views. Adding some log info I see that after the message to self.navigationController the stack is correctly popped but execution continues sequentially. The screen still shows the view of the modal ViewController.
As a workaround I tried always dismissing the modal view controller and in the ViewWillAppear method have the popToRootAnimated message. No difference. Still the stack of controllers is popped but the screen continues showing my modal view controller's view and execution continues sequentially.
Could someone help me please?
I like these deceptive questions. It seems very simple, until you try to do it.
What I found was that basically you do need to dismiss that modal view controller, but if you try to pop from the navigation controller on the next line things get mixed up. You must ensure the dismiss is complete before attempting the pop. In iOS 5 you can use dismissViewControllerAnimated:completion: like so.
-(void)GameReportModalWillBeDismissed:(BOOL)popToRoot{
if (popToRoot){
[self dismissViewControllerAnimated:YES completion:^{
[self.navigationController popToRootViewControllerAnimated:YES];
}];
}
else{
[self dismissModalViewControllerAnimated:YES];
}
}
But I see you have 4.0 in your question tags. The solution I found for <iOS 5 is far less pretty but should still work, and it sounds like you were already on the trail. You want viewDidAppear: not viewWillAppear:. My solution here involves an ivar, lets say:
BOOL shouldPopToRootOnAppear;
And then your GameReportModalWillBeDismissed: would look something like this:
-(void)GameReportModalWillBeDismissed:(BOOL)popToRoot{
shouldPopToRootOnAppear = popToRoot;
[self dismissModalViewControllerAnimated:YES];
}
And your viewDidAppear: would look like this...
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if (shouldPopToRootOnAppear){
[self.navigationController popToRootViewControllerAnimated:YES];
return;
}
// Normal viewDidAppear: stuff here
}
Related
I have looked around but haven't found a satisfying answer. My problem is that whenever I call popToRootViewControllerAnimated:(BOOL) it is not doing anything. When I NSLog it, it logs (null).
Let me back up a bit here. I have a table view controller that has a list of things, at the navigation bar up top there is an option to add and that takes me to a new view controller with a segue "Present as PopOver" which gets rid of the principal or main navigation bar. So I made one manually and added 2 bar button items "Cancel" and "Add". When "Cancel" is tapped, it should take the user back to the table view controller and discard changes, when "Add" button is tapped, it should also take user back to the previous table view controller with the changes. But it's not doing anything.
Here is my code.
- (IBAction)cancelButton:(UIBarButtonItem *)sender {
UINavigationController * navigationController = self.navigationController;
NSLog(#"%#", navigationController);
NSLog(#"cancel tapped though");
ListingTableViewController *rootController = [[ListingTableViewController alloc] init];
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:rootController animated:YES];
}
As far as the segue, this view controller is not connected to anything, or should I connect it? This is a noobish question indeed. Here is my xcode screenshot.
Check this link for the screenshot of the storyboard
http://i.stack.imgur.com/lqnCF.png
You must call
- (IBAction)cancelButton:(UIBarButtonItem *)sender {
NSLog(#"cancel tapped though");
[self dismissViewControllerAnimated:YES completion:nil];
}
instead of popToRootViewControllerAnimated because your VC presented and not pushed!
When presenting a view, you are not pushing it in your navigation controller, but having it presented. To dismiss it, try using [self.presentingViewController dismissViewControllerAnimated:NO completion:nil].
I've spent hours and I cant figure this out. I have a detail view controller (UITableView) which is launched here:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EventLocationDetailController *newDetailViewController = [[EventLocationDetailController alloc] initWithNibName:#"EventLocationDetailController" bundle:nil];
self.eventDetailController = newDetailViewController;
[self.navigationController pushViewController:self.eventDetailController animated:YES];
[newDetailViewController release];
}
In the detail view controller there is a button method which calls the below method to display a slide-in-slide-out animation confirming the users choice:
-(void)confirmLastActionWithMessage:(NSString *)message {
ConfirmActionViewController *newConfirmActionViewController = [[ConfirmActionViewController alloc] initWithNibName:#"ConfirmActionViewController" bundle:nil];
self.confirmActionViewController = newConfirmActionViewController;
[newConfirmActionViewController release];
[[self.view superview] addSubview:self.confirmActionViewController.view];
}
Protocol method called by the ConfirmActionViewController indicating that the animation is finished.
-(void)didFinishConfirmDisplay:(UIView *)viewToRemoveFromSuperview {
[viewToRemoveFromSuperview removeFromSuperview];
}
This works perfect the first time I press the button. If I pop the detail controller and push it back on to the stack and press the button again, nothing happens and the detail controller's superview is nil every time I invoke the method after that. Superview is not nil in the viewWillAppear method for the detail view, only when It gets to the confirmLastActionWithMessage method. No other user interaction happens in between. How do I get the superview back? I have a similar code that works without animation.
I've also noticed that the detail view controller hasn't called dealloc when popped off the stack. Not sure where the problem is.
Thanks.
EDIT 1
OK. I replaced the addSubview line with this one:
[self.view insertSubview:self.confirmActionViewController.view atIndex:0];
and the animation view appeared underneath one of the table cells. Could one of the table cells steal the superview?
Well I don't really understand why you should add the subview to the superview. Why not add it just to self.view
I may not be able to explain why there is no superview but try either adding the controller view to self.view or
[[[[UIApplication sharedApplication] delegate] window] addSubview:yourview];
This will render the view on top of everything.
This seems to be the patten used throughout Apples applications; Creation of a new record is done through a modal View which needs to be saved or canceled to continue, and editing a record is done through a view pushed onto the navigation stack.
It doesn't seem right to be basically duplicating my ViewController for 'add' and 'edit' but there are several differences in how pushed and modal ViewControllers work which complicate things.
How should I be doing this so it can cover both bases?
-
Differences include.
When pushed onto the stack the navBar appears at the top of the View and can be configured to contain the cancel/save buttons. When presented modally this is not the case so to duplicate the interface a toolbar needs to be created separately and close/save buttons added to this instead.
When dismissing a pushed view we send a message to the navigation controller [self.navigationController popViewControllerAnimated:YES];, when dismissing a modal view we send a message to self [self dismissModalViewControllerAnimated:YES];
You could add the UIToolbar in InterfaceBuilder, and then just hide it in viewDidLoad when self.navigationController is not nil.
As for dismissing, you could have something like:
- (void)didCancel {
[self.navigationController popViewControllerAnimated:YES] || [self dismissModalViewControllerAnimated:YES];
}
This will shortcircuit if your viewcontroller is part of a navigationcontrol, and use dismissModalViewControllerAnimated otherwise.
This should work for your cancel button. For your save button, it is useful to call some sort of delegate method such as:
- (void)didSave {
// do your saving juju here
if([self.delegate respondsToSelector:#selector(viewController:didSave:]) {
[self.delegate viewController:self didSave:whatJustGotSaved];
}
[self.navigationController popViewControllerAnimated:YES]; // noop if currently modal
}
In the delegate's implementation then, you can put:
- (void)viewController:(UIViewController*)viewController didSave:(NSObject*)whatJustGotSaved {
// do stuff with parameters
[self.modalViewController dismissModalViewControllerAnimated:YES]; // noop if not modal
}
I have 2 ViewControllers directly connected with a push segue. I am navigating from first to second view controller by calling [self performSegueWithIdentifier:#"segueIdentifier" sender:sender]. On the second one I have an IBAction method that is bound to a "Done" button. Pressing that button should basically cause the first view controller to be displayed (sort of a back button). I managed to do that with:
NSArray *viewControllers = self.navigationController.viewControllers;
[self.navigationController popToViewController:[viewControllers
objectAtIndex:0] animated:YES];
I did try to achieve the same effect by using:
[self dismissViewControllerAnimated:YES completion:nil];
No matter what I tried though this didn't do the job. I am trying to understand what exactly am I missing but I can't figure it out. Does dismissViewControllerAnimated method work only with Modal segues ( this is the only thing that came to mind ).
Thank you
Yes,
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
is when a UIViewController is displayed modally.
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
should do what you are seeking.
So basically, in your second VC:
[self.navigationController popViewControllerAnimated:YES];
You will save you a lot of trouble if you read the UIViewController and UINavigationController references. Twice ;)
I'm showing UINavigationController modally.
For the root view controller, I don't want to show the Navigation bar.
However for deeper controllers, I do want to show it.
I though of doing something like this inside my root view controller :
-(void) viewWillAppear:(BOOL)animated
{
[self.navigationController.navigationBar setHidden:YES];
}
-(void) viewWillDisappear:(BOOL)animated
{
[self.navigationController.navigationBar setHidden:NO];
}
However, this presents issues, when I go back from the first view controller to the root view controller.
The navigation bar is disappearing after pressing the "back" button (inside the first view controller, leaving white space), and not only after the rootViewController finished loading. (Obviously because my code uses viewWillAppear)
Is there a solution for it ?
The only thing I thought of is hidiing the navigation bar permanently, and adding Navigation bar manually to each view controller in the stack.
I hope not to do that since it's much more work, and moreover, I want to use arrow shaped buttons, for which I would have to create custom images.
Appreciate any suggestions.
This should do it, I haven't tested it out, but should work in theory:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.navigationController setNavigationBarHidden:NO animated:YES];
}