Trying to understand dismissing ViewControllers - objective-c

I was trying to dismiss a view controller and there are several methods to call that all seem to similar. #1 worked but the others didn't. When should I use one of these but not the other?
1. [self dismissViewControllerAnimated:YES completion:nil]; (this one worked).
2. [self.parentViewController.navigationController popViewControllerAnimated:YES];
3. [self.navigationController popViewControllerAnimated:YES];
4. [self performSegueWithIdentifier:#"showPreviousController" sender:self];
5. [self.navigationController popToRootViewControllerAnimated:YES];

[self dismissViewControllerAnimated:YES completion:nil];
This works fine if you don't have navigation controller and you want to jump back to previous view controller.
Next button jumps to view controller B and when dismiss button is pressed it call's dismissViewControllerAnimated: method and jumps back to first view controller.
[self.parentViewController.navigationController popViewControllerAnimated:YES];
Quote from Apple's documentation about parentViewController:
If the recipient is a child of a container view controller, this property holds the view controller it is contained in. If the recipient has no parent, the value in this property is nil.
[self.navigationController popViewControllerAnimated:YES];
This jumps back to previous view controller. You need navigation controller set up to use this method. Otherwise nothing happens.
If you press Dismiss button in view controller B and that button calls popViewControllerAnimated: method, it will jump back to view controller A.
[self performSegueWithIdentifier:#"showPreviousController" sender:self];
This performs seque with identifier as it says. In storyboard you can set identifier for seques.
When you have seque selected, you can set identifier in Attributes inspector:
[self.navigationController popToRootViewControllerAnimated:YES];
This jumps back to first view controller. Called root controller. So if you have 5 view controllers (A -> B -> C -> D and E) and you call popToRootViewControllerAnimated: at E, it will jump back to A-controller.

Those methods all do different things, I suggest you read the View Controller Programming Guide, since you are mixing up several concepts.
However here's a summary of what they do:
[self dismissViewControllerAnimated:YES completion:nil]; (this one worked).
Dismiss the view controller presented by this controller (self).
[self.parentViewController.navigationController popViewControllerAnimated:YES];
Pop the top view controller in the parent's navigation controller. This is weird and probably wrong. parentViewController is the view controller the current one is embedded in. You are accessing its navigation controller and making it pop the view controller on top of the stack. It will silently fail in case:
parentViewController is nil
navigationController is nil
[self.navigationController popViewControllerAnimated:YES];
Pop che view controller on top of the current navigation stack. Makes sense in case the current controller has been pushed by the navigation controller (and not presented modally).
[self performSegueWithIdentifier:#"showPreviousController" sender:self];
Well, this is performing the showPrviousController segue, whatever it does...
[self.navigationController popToRootViewControllerAnimated:YES];
This make the current navigation controller to pop all the view controllers, except the root one, which will remain on top.

Related

iOS7 - popToRootViewControllerAnimated not doing anything

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].

Load view with the navigation controller?

I have an app where my main view is embedded in a navigation controller. From there, buttons push onto other view controllers. This all works fine. However, one of the view controllers it pushes to updates one of the root's values and presents it again. However this time, it only presents the ViewController without the navigation controller, and of course, pressing the button to go back will end in a crash. Hopefully this picture will help understand my issue. The pressing enter thing isn't really a big deal, I just call this function on return of the keyboard.
Code to go back to the main controller:
-(void)createNewMain:(NSString*)newAddress {
ViewController* newController = [self.storyboard instantiateViewControllerWithIdentifier:#"MainView"];
newController.labelText = newAddress;
newController.connected = self.connected;
[self presentViewController:newController animated:YES completion:nil];
}
The problem is simple, you're presenting the instantiated view controller modally.
Replace
[self presentViewController:newController animated:YES completion:nil];
with
[self.navigationController pushViewController:newController animated:yes];
Also, you can make a segue to do that from the storyboard. When the segue executes, it will create a new instance and will not use the previously created one.
Note: If you really don't need to create a new instance, consider using delegation to exchange information between objects.
You don't really want to "return" to a new instance of a root controller. What you need to do to properly return to the root controller is to pop all the other ones from the navigation controller's stack like this:
[self.navigationController popToRootViewControllerAnimated:YES];
Use Delegation to pass the expected/required message you want from your Pi Controller to your Root View Controller and set it up according to the message. You don't need to create a new instance of your Root View Controller from there. You can always go back to your root view controller from anywhere in the navigation stack by using
[[self navigationController] popToRootViewControllerAnimated:YES];

Does presentModalViewController: add the view controller to the stack?

I have a main navigation controller with a root view controller. In the root view controller, on the push of a button I present second view controller like this:
SecondVC *secondVC = [[SecondVC alloc] initWithNibName:#"SecondVC" bundle:nil];
[self.navigationController presentModalViewController:secondVC animated:YES];
In the second view controller, on the push of an other button, I want to present a third view controller (this time from a Storyboard):
ThirdVC *thirdVC = [[UIStoryboard storyboardWithName:#"Settings" bundle:nil] instantiateInitialViewController];
[self.navigationController presentModalViewController:thirdVC animated:YES];
However this doesn't do anything. I debugged and it turned out, that self.navigationController is nil.
Shouldn't it be the main navigation controller? Or doesn't presentModalViewController: add the view controller to the stack? Do I always have to put a view controller in a navigation controller before presenting id modally?
The new view controller SecondVC is being presented modally, and it's not added to the view controller stack of the navigationController. You need to create a new UINavigationController, and put SecondVC inside the navController before presenting it modally.
You'll need to add something like:
UINavigationController *navControl = [[UINavigationController alloc] initWithRootViewController:secondVC];
[self addChildViewController:navController];
[self.navigationController presentModalViewController:secondVC animated:<#(BOOL)#>]
your view controller while being presented is not inside a navigation controller. And will not have access to the presenting controllers navigation controller.
Furthermore if you push or pop stack items on the navigation controller beneath the modal view controller you will likely not notice anything.
If you want to put the controller in the stack you can alternatively show the view controller yourself.
[self.view addSubView:myViewController.view]
myViewController.view.frame = self.view.bounds;
and to dismiss the view controller you would simply remove it from its superview.
the drawback here is that some of the did and will appear methods are not called on the view controller. Therefore you may want to call them yourself.
But the principal is much the same. And you can easily simulate the presenting animation with the animation system.
Give it a starting point below your form, then start your animation block and put the view.frame to superview.bounds also giving it an animation time. I find that 2 seconds is ok. sometimes less.
at this point the presented view is inside the controller which is on the stack. Now while you cant directly modify the navigation controller within the presented view controller you could set a delegate that tells the original your intentions and therefore the presenting view controller (the one on the navigation stack) can push or pop the view controllers as requested. And the presented view controller will be pushed along with it.
Another positive point is that you can do much like other apps do, and present a semi modal view. With a partially transparent background. this way you can show things happening behind the view even tho they dont directly manipulate it.

Should I use the same detail ViewController to work both modally and when pushed?

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
}

Navigation controller problem

I have a normal view controller and I want to add a uinavigationcontroller to it so:
[self.view addSubview:aNavigationController.view];
everything works, fine, aNavigationController is an IBOutlet, in the XIB, it's view controller is loaded from another xib, then in the navigation controller's view controller's class I type this:
- (IBAction)anAction {
[self.navigationController pushViewController:aViewController animated:YES];
}
everything works fine, the view changes to the aViewController view and it's animated, but when I type in aViewController's class this:
- (IBAction)anotherAction {
[self.navigationController popViewControllerAnimated:YES];
}
it crashes, why?
Because there is no view to pop. When you're trying to pop view controller it is expected that there is some view in stack, i.e. the view from which you calling popViewControllerAnimated was already pushed earlier.
So popping is not just awesome animation but navigation through stack of views in navigation controller. In this particular situation you're trying to call -1st element of this stack, that is the reason of the crash.
Dig deeper here:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/NavigationControllers/NavigationControllers.html#//apple_ref/doc/uid/TP40007457-CH103-SW1