dismissViewControllerAnimated:Completion: Concept? - ios

I have 3 viewControllers - BaseViewController->AviewController->BviewController.
AviewController is presented modally on BaseViewController and BviewController is presented modally on AviewController.
In AviewController if I call [self dismissViewControllerAnimated:Completion] it dismisses both AviewController and BviewController.
In BviewController if I call [self.presentingViewController dismissViewControllerAnimated:completion] it only dismisses BviewController.
Why is AviewController not dismissed ?
Is this concept wrong AviewController = BviewController.presentingViewController. ?
I also tried taking weak reference of AviewController in BviewContrroller and tried to dismiss. But still only BviewController alone is dismissed.
i.e [AviewControllerReference dismissViewControllerAnimated:completion]
Any mistake in my understanding of the concept?

From apple 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.
So you must call this method on presenting VC to dismiss the presented controller. Not presented controller it self. As you can see in above in bold test, if you called this on a presented VC it automatically forwards the method to prsenting VC.
So to answer to your issue,
Call below line on BViewController to dismiss both AViewcontroller and BViewController. ,
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
this is Equalent to calling dismissViewController: on your BaseViewController. Which is the correct way of dismissing AViewController

Related

How does dismissing a presented UIViewController work?

Apple's View Controller Programming Guide for iOS states:
To dismiss a presented view controller, call the dismissViewControllerAnimated:completion: method of the presenting view controller. You can also call this method on the presented view controller itself. When you call the method on the presented view controller, UIKit automatically forwards the request to the presenting view controller.
This is verified by the UIViewController 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, UIKit asks the presenting view controller to handle the dismissal.
However this behavior seems ambiguous to me. What if a UIViewController is both presented and presenting? If you call dismiss(animated:completion:) on it, will it pass the call to its presentingViewController which will dismiss it, or will it dismiss its presentedViewController?
For example, suppose I have a UIViewController vc1, which creates and presents a UIViewController vc2, which then creates and presents vc3. So vc1 has vc2 as its presentedViewController, vc2 has vc1 as its presentingViewController and vc3 as its presentedViewController, and vc3 has vc2 as its presentedViewController. Then dismiss(animated:completion:) is called on vc2. Which is dismissed, vc2 or vc3? If vc2 is dismissed, what happens to vc3?

All the presented view controllers are not dismissed

I have a firstViewController that I display with
[self.navigationController pushViewController:firstViewController animated:true];
Then when a button in this first VC is clicked, I present a second one:
[self presentViewController:secondViewController animated:true completion:nil];
And from this second one I present the third VC as a modal:
[thirdVC modalPresentationStyle];
[thirdVC setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self presentViewController:thirdVC animated:true completion:nil];
The problem is that I want to display the firstVC when the validate button of the last VC is clicked. So I have to dismiss the third and the second one. I tried this in the validateAction (in the third VC):
[[self parentViewController] dismissViewControllerAnimated:true completion:nil];
[self dismissViewControllerAnimated:true completion:nil];
But the result is that the thirdVC (the modal one) is dismissed, and from the debugger I can see that the firstVC is covered by the secondVC.
How can I dismiss the second and the third VC at the same time in order to return to the first one?
NOTE: I want to push the firstVC after the dismiss of the others because I have to reload the data (in the fristVC viewDidLoad)
From Apple's 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, UIKit asks the presenting view controller to handle the dismissal.
In other words, the third viewController asks the second viewController to dismiss its presented viewController, which is the third one. Then the third viewController tries to dismiss itself, causing UIKit to ask the second viewController to dismiss the third one once more.
You will have to go one layer up and ask the first viewController to dismiss its presented view controller.
It is good practice that the third viewController does not know anything about the presentation hierarchy. You can use delegation to tell the first viewController that the task is finished. An exit segue is a very nice alternative if you use storyboards.
You only have to call -dismissViewControllerAnimated:completion: once if you address the first viewController:
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.

How can I dismiss a UINavigationController presented modally?

I have the following structure:
UINavigationController(1) -> MainViewController ---PRESENT MODAL--->
UINavigationController(2) -> TutorialViewController ---PRESENT MODAL---> LoginViewController
---PRESENT MODAL---> SignupViewController
I want to dismiss the UINavigationController(2) from a user action on LoginViewController.
Any idea?
You should use delegation to dismiss the second view controller. So you would have the second nav controller be dismissed by the first nav controller, the one that presented it. This is the recommended way to dismiss modal views.
Here are the docs on delegation

poptorootviewcontroller and dismiss to mainViewController

i am new to IOS .
My question is, i have some view-controllers as NavigationController,mainVC, VC1, VC2, VC3, CameraVC. In cameraVC i have a done button having action doneClicked. These all View-Controller are pushed in NavigationController. VC1 is presented, not pushed in nav-controller. doneClicked function implemented poptorootviewcontroller. when i click on done button it let me to VC1 but not the mainVC. Is there any way so i can pop all view controller to VC1 and after this automatically dismiss VC1 to mainVC.
Make your MainVC as root view controller and in the IBAction of done button use the code to pop to MainVC.
[self.navigationController popToRootViewControllerAnimated:YES];
Hope it helps.
To pop view controllers
[self.navigationController popToRootViewControllerAnimated:YES];
and to dismiss presented view controller
[self dismissViewControllerAnimated:NO completion:nil]
[self.navigationController setViewControllers:#[mainVC]];
I think this code will work on your situation. iOS Developer Library:
Replaces the view controllers currently managed by the navigation controller with the specified items.
- (void)setViewControllers:(NSArray *)viewControllers
animated:(BOOL)animated
source
you must do this after dissmiss present view controller. Use delegate
Now let's think your navigation stack is empty and your root is mainVC. you want to present VC1 that's ok just present it. But you should give a delegate to mainVC to what's gonna happen after dissmissing VC1. For example you present VC1 from mainVC. And you want to push VC2 after dissmiss VC1. That's ok just in mainVC has a delegate so in this method
[self.navigationController pushViewController:VC2];
Present views does not affect your navigation stack. It's not in your stack. so everytime you dismiss it from a controller you should give an delegate to that controller to what will happen after dissmissing.
Try it.
Hope it helps.

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

Resources