dismissing a stack of view controllers - ios

According to the documentation dismissViewControllerAnimated:completion: will allow you to dismiss an entire stack of view controllers by merely dismissing the controller on the bottom of the stack. When I try this, only the top controller is being dismissed.
For example, consider the figure below where controller A and B have both been presented modally. If I dismiss A then both A and B should be dismissed, but only B is being dismissed!
Below is the action that is trigged when the user click on B's button. The root view controller dismisses A (its presented controller), but only B is dismissed!!
- (IBAction)dissmissAandB:(id)sender {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
ViewController *rootViewController =
(ViewController *) appDelegate.window.rootViewController;
[rootViewController.controllerA dismissViewControllerAnimated:YES completion:^{}];Below
}
The root view controller's controllerA property is set when the first segue is prepared (i.e., when A is presented). Any ideas why this is not working as advertised?

Actually, I discovered the correct call is
[rootViewController dismissViewControllerAnimated:YES completion:^{}];
instead of
[rootViewController.controllerA dismissViewControllerAnimated:YES completion:^{}];
The receiver of the method is the controller that presented the bottom controller not the bottom controller itself.

Related

ViewController WILL NOT dismiss

WLINewPostViewController *newPostViewController = [[WLINewPostViewController alloc] initWithNibName:#"WLINewPostViewController" bundle:nil];
UINavigationController *newPostNavigationController = [[UINavigationController alloc] initWithRootViewController:newPostViewController];
newPostNavigationController.navigationBar.translucent = NO;
[tabBarController presentViewController:newPostNavigationController animated:YES completion:nil];
So I just simply push a new UIViewController.
Then after it posts the server callback calls a method with this code from the WLINewPostViewController.m:
[self dismissViewControllerAnimated:YES completion:^{
NSLog(#"Completed");
}];
[[self navigationController] popViewControllerAnimated:YES];
if (self == self.navigationController.visibleViewController){
NSLog(#"self = visibile");
}
if (self == self.presentingViewController.presentingViewController){
NSLog(#"self = presenting");
}
}
I tried a bunch of different things and none work.
I am relatively new to Xcode but after trying
[self dismissViewControllerAnimated:YES completion]
[self.navigationController popViewControllerAnimated:YES]
[self.navigationController.visibleViewController.presentedViewController dismissViewControllerAnimated:YES completion:nil];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
and every other possibility, I am officially stumped. The WLINewPostViewController still won't dismiss.
It Logs out "self = visible"
Let me illustrate what you are trying to do
You have a navigation controller with Controller A.
Here you are trying to present another Controller B from Controller A.
Now when you get a callback from the server, you should call dismissViewControllerAnimated from Controller B to dismiss itself.
So after dismissViewControllerAnimated:completion: method call, the Controller B will be dismissed and Controller A will be shown automatically. Now you do not need to call popViewControllerAnimated: in completion block again as there is no other Controller in navigation controller to load.
If you have different use case, let me know I can provide solution.
You are presenting a view over navigationbar instead of pushing it over navigationbar.
When push you pop. When you present you dismiss. So instead of popViewControllerAnimated you need to use dismissViewControllerAnimated:completion
dismiss behaves differently depending on the receiver. From the docs:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
If you present several view controllers in succession, thus building a stack of presented 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.
In short, if the vc on top calls it on itself, it dismisses itself. Anywhere else on the stack dismisses to that point, animating only the topmost vc.
What's extra confusing (for you and many others) is that the navigation vc has a stack too, and your problem is complicated further by presenting an navigation vc atop a tab-bar vc.
So what to do? The question is unclear about which vc is the receiver in the posted code (who is self in that snippet?). The text implies that self is a vc on the stack of the presented navigation vc, like...
TabBarVC --- presents ---> NavVC
| |
| --- viewControllers stack = rootVC, vc1
|
---> viewControllers for each tab
... and it's root or vc1 that wants to dismiss. If I'm right about that, then, given the docs, the solution is clear:
[self.navigationController dismissViewControllerAnimated:YES completion:^{}];
will put us back on the tabbar vc on whatever tab was visible when we did the present.

Dismissing a ViewController lower in the stack does not behave as expected

I'm building a complex app that has kind of a branch in the middle.
At some point in the app, a particular UIViewController is presented, we'll call it mainViewController (shortened mainVC).
The mainVC presents another view controller, by code, using the following code (I strip out parts of it for privacy reasons):
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"SecondaryStoryboard" bundle:secondaryBundle];
SecondViewController *secondVC = [storyboard instantiateInitialViewController];
[self presentViewController:secondVC animated:YES completion:nil];
So the secondVC will later present another view controller, called thirdVC. This is done using a custom segue, set in the storyboard used in the code above, which code looks like this:
#implementation VCCustomPushSegue
- (void)perform {
UIView *sourceView = ((UIViewController *)self.sourceViewController).view;
UIView *destinationView = ((UIViewController *)self.destinationViewController).view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
destinationView.center = CGPointMake(sourceView.center.x + sourceView.frame.size.width, destinationView.center.y);
[window insertSubview:destinationView aboveSubview:sourceView];
[UIView animateWithDuration:0.4
animations:^{
destinationView.center = CGPointMake(sourceView.center.x, destinationView.center.y);
sourceView.center = CGPointMake(0 - sourceView.center.x, destinationView.center.y);
}
completion:^(BOOL finished){
[self.sourceViewController presentViewController:self.destinationViewController animated:NO completion:nil];
}];
}
#end
As you can see this segue presents the destination view controller modally (by the use of presentViewController:) with a custom animation (a slide from right to left).
So basically up to here everything is fine. I present the secondVC with a classic modal animation (slide up from bottom) and present the thirdVC with my custom transition.
But when I want to dismiss the thirdVC, what I want is to go back directly to the mainVC. So I call the following from the thirdVC :
self.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:_animate completion:nil];
That way, I'm calling dismissViewControllerAnimated: directly on mainVC (referenced by self.presentingViewController.presentingViewController), and I'm expecting the thirdVC to be dismissed with an animation, and the secondVC to just disappear without animation.
As Apple says in the UIViewController Class Documentation:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, it automatically forwards the message to the
presenting view controller.
If you present several view controllers in succession, thus building a
stack of presented 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.
The issue is that it's not what happens. In my scenario, the thirdVC disappears, and shows the secondVC being dismissed with the classic modal slide to bottom animation.
What am I doing wrong ?
Edit :
So #codeFi's answer is probably working in a classic project, but the problem here is that I'm working on a framework. So mainVC would be in a client app, and the secondVC and thirdVC are in my framework, in a separate storyboard. I don't have access to mainVC in any other way than a reference to it in my code, so unwind segues are unfortunately not an option here.
I've been having this exact same issue, and I've managed to visually work around it by adding a snapshot of the screen as a subview to secondVC.view, like so:
if (self.presentedViewController.presentedViewController) {
[self.presentedViewController.view addSubview:[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]];
}
[self dismissViewControllerAnimated:YES completion:nil];
Not pretty, but it seems to be working.
NOTE: if your secondVC has a navigation bar, you will need to hide the navigation bar in between snapshotting the screen and adding the snapshot as a subview to secondVC, as otherwise the snapshot will appear below the navigation bar, thus seemingly displaying a double navigation bar during the dismissal animation. Code:
if (self.presentedViewController.presentedViewController) {
UIView *snapshot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
[self.presentedViewController.navigationController setNavigationBarHidden:YES animated:NO];
[self.presentedViewController.view addSubview:snapshot];
}
[self dismissViewControllerAnimated:YES completion:nil];
I had the same issue and I've fixed it by using UnwindSegues.
Basically, all you have to do is add an IBAction Unwind Segue method in the ViewController that you want to segue to and then connect in IB the Exit action to your Unwind Segue method.
Example:
Let's say you have three ViewControllers (VC1, VC2, VC3) and you want to go from VC3 to VC1.
Step 1
Add a method to VC1 like the following:
- (IBAction)unwindToVC1:(UIStoryboardSegue*)sender
{
}
Step 2
Go in Interface Builder to VC3 and select it. Then CTRL-drag from your VC icon to Exit icon and select the method you've just added in VC1.
Step 3
While still in IB and with VC3 selected, select your Unwind Segue and in the Attributes Inspector add a Segue Identifier.
Step 4
Go to VC3 where you need to perform your segue (or dismiss the VC) and add the following:
[self performSegueWithIdentifier:#"VC1Segue" sender:self];

Back to RootViewController from Modal View Controller

From Home view - my RootViewController - I open up 2 ViewControllers one after another as user progresses in navigation hierarchy like so:
1) SecondViewController is pushed by button connected in my Storyboard
2) ThirdViewController is presented modally
[self performSegueWithIdentifier:#"NextViewController" sender:nil];
So, the picture is: RootViewController -> SecondViewController -> ThirdViewController
Now in my ThirdViewController I want to have a button to go back 2 times to my RootViewController, i.e. go home. But this does not work:
[self.navigationController popToRootViewControllerAnimated:YES];
Only this guy goes back once to SecondViewController
[self.navigationController popViewControllerAnimated:YES];
How can I remove both modal and pushed view controllers at the same time?
I had a similar situation, where I had a number of view controllers pushed onto the navigation controller stack, and then the last view was presented modally. On the modal screen, I have a Cancel button that goes back to the root view controller.
In the modal view controller, I have an action that is triggered when the Cancel button is tapped:
- (IBAction)cancel:(id)sender
{
[self.delegate modalViewControllerDidCancel];
}
In the header of this modal view controller, I declare a protocol:
#protocol ModalViewControllerDelegate
- (void)modalViewControllerDidCancel;
#end
And then the last view controller in the navigation stack (the one that presented the modal view) should implement the ModalViewControllerDelegate protocol:
- (void)modalViewControllerDidCancel
{
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:YES];
}
This method above is the important part. It gets the presenting view controller to dismiss the modal view, and then it pops back to the root view controller. Note that I pass NO to dismissViewControllerAnimated: and YES to popToRootViewControllerAnimated: to get a smoother animation from modal view to root view.
I had the same requirement but was using custom segues between the view controllers. I came across with the concept of "Unwind Segue" which I think came with iOS6. If you are targeting iOS6 and above these links might help:
What are Unwind segues for and how do you use them?
http://chrisrisner.com/Unwinding-with-iOS-and-Storyboards
Thanks.
Assuming your AppDelegate is called AppDelegate, then you can do the following which will reset the rootviewcontroller for the app window as the view RootViewController
AppDelegate *appDel = (AppDelegate*)[[UIApplication sharedApplication] delegate];
RootViewController *rootView = [[RootViewController alloc] init];
[appDel.window setRootViewController:rootView];

IOS How to call a method in a ViewController if modal is dismissed

In all view controllers I present a ModalViewController after I dismiss modal view controller I want to call a method in current view controller.
I have to presentModalViewController I cant push it because it is a form sheet. Since I cant push it (void)viewDidAppear:(BOOL)animated is not called when I dismiss the form sheet.
Btw form sheet is a settings menu and I have to call it in every view controller, so I cant use notifications because there are over 20 View controllers and only one settings menu;
Navigation controller -> Root- > VC1 - > VC2 - > VC3 ->VC4........... VC20......
| | | | |
Menu Menu Menu Menu Menu
I present menu:
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle:nil];
SettingsListViewController *settingsVC = [sb instantiateViewControllerWithIdentifier:#"SettingsListViewController"];
UINavigationController *modalViewNavController= [[UINavigationController alloc] initWithRootViewController:settingsVC];
modalViewNavController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
modalViewNavController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:modalViewNavController animated:YES];
I dismiss it :
//dissmiss view
[self.navigationController dismissModalViewControllerAnimated:YES] ;
In View Controllers I want to Call;
[self.navigationController popToRootViewControllerAnimated:NO];
Is there a way to call a method in View Controller when form sheet is dismissed ?
Since iOS 5, you can use the presentingViewController property of every UIViewController to see 1) if they're being presented modally in the first place and 2) who it is that's presenting them modally then. So if you present your form sheet by calling [self.navigationController presentModalViewController:modalViewNavController animated:YES], the presenting view controller will then be the root navigation controller and you can tell it to pop to root at the same time you dismiss the modal presentation.
By the way, there's also a storyboard property in every view controller which has originated from a storyboard, so you could use that one directly when instantiating new storyboard view controllers by name.
Make your own delegate and set the view controller that presents the view as delegate.. and call from modalVC when it is about to be dismissed.
You can set modalViewController.parentViewController = self; and then work with it from modal view controller like that if you want to send messages before dismiss:
- (void)viewWillDisappear:(BOOL)animated {
[self.parentViewController doSomething];
}

dismissViewControllerAnimated VS popViewControllerAnimated

I want to know what is the different of them.
when I can call
[self dismissViewControllerAnimated:YES completion:nil];
and when I should call
[self.navigationController popViewControllerAnimated:YES];
according document of apple:
dismissViewControllerAnimated means
"Dismisses the view controller that was presented by the receiver."
But I always fail to dismiss view controller by this method.
-dismissViewControllerAnimated:completion:
Used to dismiss an UIViewController, which was presented by the method:
-presentViewController:animated:completion:.
-popViewControllerAnimated:
Method of UINavigationController is used to pop a controller shown by
-pushViewController:animated method of UINavigationController.
In the first case the view controller's view shows as a modal controller (usually from bottom to top), and the second case you are pushing a view controller in the navigation stack of UINavigationController.
your selected application is navigation based application means
[self.navigationController popViewControllerAnimated:YES];
your selected application is other than the navigation based application means
[self dismissViewControllerAnimated:YES completion:nil];

Resources