Dismissing both UINavigation views and Modal views at once programmatically - ios

I have tried a few answers on this site, but none of them seem to relate to my problem
I have a MasterDetail app which has a two types of segues that I am using. When you press a button on the detail view, it uses a push segue and pushes another detail view onto that. In the new detailview (the one that was just pushed on) there is a button which calls up another UIView (form sheet) using a modal segue.
What I am trying to do is when the user selects a row, a UIAlertView will show up displaying a message, while at the same time (doesn't have to be at the same time) it dismisses the UIViewcontroller (modal) and goes back from the Viewcontroller that was pushed on. Basically, I need to be able to dismiss all viewcontrollers, one modal and one push (nav) so that the view returns to the original main screen that they started with.
I have the UIAlertView working fine and I can get the modal viewcontroller to dismiss by using [self.dismissViewControllerAnimated:YES completion:nil]; but I don't know how to dismiss the next Viewcontroller (which is in a navigation controller). Using this: [self.navigationController popToRootViewControllerAnimated:NO]; does not work.
Here is where I want to call the function to remove all views:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlWithIDAndChallenge];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
UIAlertView *message = [[UIAlertView alloc] initWithTitle#"Started" message:#"The challenge has begun!" delegate:nil cancelButtonTitle:#"OK!" otherButtonTitles:nil];
[message show]
//right here is where I would normally dismiss the modal VC
//here is where I would like to dismiss all VC
}

If you want, in iOS 6.0 (and later) projects you can use an unwind segue. For example, you can:
In your top level view controller (the one you want to unwind to, not the controller you're going to unwind from), write an unwind segue method, in this case called unwindToTopLevel (personally, I find it useful if the segue bears some indication as to what the destination of the segue is, for reasons that will become apparent when we get to step 3, below):
- (IBAction)unwindToTopLevel:(UIStoryboardSegue *)segue
{
NSLog(#"%s", __FUNCTION__);
}
In the Interface Builder scene from which you will initiate the unwind, control ⌘-drag from the view controller icon to the exit icon to create the unwind segue:
Generally you'd define the segue from a button to the exit outlet (and you're done), but if you want to invoke the segue programmatically, you might want to create it between the controller and the unwind outlet, like shown above.
You'll get a little pop up that includes the name of your unwind segues (from the presenting controllers ... it's like magic):
If you're going to invoke that segue programmatically, you have to select that unwind segue in the document outline on the left side of the IB window and once you've done that, you can give the unwind segue a storyboard id:
I generally use the name of the the unwind segue for the storyboard id, but you can use whatever you want.
Now, having done that, your alert view can invoke the segue:
- (IBAction)didTouchUpInsideButton:(id)sender
{
[[[UIAlertView alloc] initWithTitle:nil message:#"go home" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil] show];
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex != [alertView cancelButtonIndex])
{
[self performSegueWithIdentifier:#"unwindToTopLevel" sender:self];
}
}

From the class you presented your modal view
call dismiss of modal and then perform selector after some delay and then do the
here is the sample code
//Write this in the class from where you presented a modal View.
//Call the function from modal class when you want to dismiss and go to root.
- (void) dismissAndGoToRoot {
[self dismissViewControllerAnimated:YES completion:nil];
[self performSelector:#selector(gotoRoot) withObject:nil afterDelay:0.50];
}
- (void)gotoRoot {
[self.navigationController popToRootViewControllerAnimated:NO];
}
Here you will call the function
//right here is where I would normally dismiss the modal VC
//here is where I would like to dismiss all VC
[self.parentViewController dismissAndGoToRoot];
If this does not work then take an instance variable of ParentViewController in modal class and assign when presenting modal view controller.

You could try replacing [self.dismissViewControllerAnimated:YES completion:nil]; with
self dismissViewControllerAnimated:YES completion:^{
[parentViewController.navigationController popToRootViewControllerAnimated:YES];
}
I believe parentViewController will point to the presenting view controller, and the block will cause the parent view controller's navigation controller to pop to the root view controller.

Related

Dismiss Modal View Controller and Simultaneously Segue from Sending to Different View Controller in IOS

I launch a modal view controller VCM embedded in a Navigation controller from VC1.
The modal VC has a button to go to another View Controller, VC2.
if the user presses the button to go to VC2, I need to simultaneously dismiss the modal and also change the sending from VC1 to VC2. (It's important to dismiss the modal to get rid of it, so I don't have VCs accumulating on the stack.)
Here is the action method in the modal attached to the VC2 button.
- (IBAction)VC2ButtonPressed:(id)sender {
[self dismissViewControllerAnimated:true completion:nil];
//Above line works fine
[VC1 performSegueWithIdentifier:#"showVC2" sender:VC1];//No known class method error
}
I can dismiss the modal without any problem (first line) but can't figure out how to simultaneously segue to VC2. There is a storyboard segue in VC1 named 'showVC2' for what it's worth. I do not have any relevant class methods currently in the sending VC. Should I create a special class method? Or how can I invoke a method in the VC that launched the modal.
Edit:
I also tried:
[self.presentingViewController performSegueWithIdentifier:#"showVC2" sender:self];
This compiles but gives runtime error () has no segue with identifier 'showVC2''
I think this is because VC2 is embedded in a tabbarcontroller.
Edit2:
I tried putting code in completion handler as suggested by #Oscar but while code gets fired, VC2 does not load
[self dismissViewControllerAnimated:YES completion:^{
[self gotoVC2];
}];
new method
-(void)gotoVC2 {
LogDebug(#"gotoVC2 called");//DOES log to console
UIStoryboard *storyBoard = self.storyboard;
IDFeedVC *VC2 =
[storyBoard instantiateViewControllerWithIdentifier:#"vc2"];
[self presentViewController:VC2 animated:YES completion: nil];
}
Thanks for any ideas.

Difference between `present` & `push` ViewController

dismissViewController method not working to get previous viewcontoller
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
popViewController work and go to previous viewcontroller
[self.navigationController popViewControllerAnimated:YES];
i want to pass data like below please help me.but as i describe dismissViewController method not working.
[self.navigationController dismissViewControllerAnimated:YES completion:^{
HomeVC *loading;
loading.IdNameLabel.text=display;
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Code" message:display delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}];
thank in advance.
Update
-(void)viewWillAppear:(BOOL)animated{
self.IdNameLabel.text=self.GetscanResult;
}
this method contain in HomeVC.when dismiss navcontroller it shows HomeVC.
is upper method can call after dismiss viewcontroller?
SOLUTION
this is very stupid question when i am at beginner level.
here is specification about it.
it will depend on method which will you Present || Push viewController.
Delegate methods will work with protocol delegate method when view controller present.
The method you need to use to go back depends on how you showed the view controller.
The first of the two common ways are by using a navigation view controller to push and pop view controllers off the stack.
The other common way is to present and dismiss a view controller modally.
You cannot for example present a view controller modally, and expect to be able to pop it off from the navigation controller, or vice versa.

Generic way of Popping & Dismissing View Controllers

I am building an application in which, i have 3 ViewControllers.
Also i have created custom navigation controller.
A-ViewController -> This contains 2 buttons, 1st button is for opening B-ViewController & 2nd button is for C-ViewController.
From 1st button, I am using pushviewcontroller for B-ViewController, means B-ViewController is pushed from A-ViewController.
From 2nd button C-ViewController is presented using presentviewcontroller.
Now on pressing back button in both B & C ViewControllers, I have to use pop view controller in B-ViewController & dismiss in C-ViewController.
As currently i know the pages, but there should be a generic solution.
Is there any way to identify whether current navigation controller is pushed or popped.
As there are some pages which can be pushed or presented, but i dont want to set any bool variable. I need use the support from apple.
NSArray *arrViewControllers = [[AppDelegate sharedInstance].navigationController viewControllers];
NSLog(#"[arrViewControllers count] = %d",[arrViewControllers count]);
I am using the above code for fetching the list of view controllers in the stack.
But i am not able to identify whether it is pushed or presented.
Can anybody help me in this ?
You can check if your view controller exists in the navigation view controller's stack. Simply check it as
if([self.navigationController topViewController] == self){
//VC is the top most view controller
[self.navigationController popViewControllerAnimated:YES];
}else{
//You can put some checks here to be dead sure its a modally presented view controller
[self dismissViewControllerAnimated:YES completion:nil];
}
This is the code for Finding the navigation controller is presented or pushed from previous controller.
NSArray *arrViewControllers = [[AppDelegate sharedInstance].navigationController viewControllers];
UIViewController *viewController = [arrViewControllers lastObject];
if (viewController.presentedViewController)
{
[self dismissViewControllerAnimated:YES completion:nil];
}
else{
[self.navigationController popViewControllerAnimated:YES];
}

How to call method on presenting view controller from modal view controller

I have a modal view controller that I called in from another view controller. Upon dismissal of the modal view controller, I want a method to be called on the view controller that presented that modal view. What is the easiest way to do this?
I tried doing this in my modal view controller: [(ParentViewController*)self.presentingViewController foo]; before calling [self dismissViewControllerAnimated:YES completion:nil];.
Xcode gives me an error saying foo isn't recognized, even though it is defined and prototyped in the controller. If your solution involves blocks, I really don't understand them so I would appreciate it if you would add more detail. Thanks.
ParentViewController.h
#interface ParentViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
{
NewAssignmentViewController *newAssignmentViewController;
TableViewDataSource *data;
}
-(void)foo;
#end
You need to get a correct reference to your presenting controller like this:
ParentViewController *presenter = [(UITabBarController *)self.presentingViewController viewControllers][0]; // 0 is assuming that ParentViewController is in the first tab. Change if necessary
[presenter foo];
The other way to do it would be to use delegation, but that's an answer for another time.
If you are using a Storyboard segue to present your view controller you could dismiss it using an Unwind Segue. An unwind segue is a special kind of segue that unwinds the presented view controllers back to a presenter.
To accomplish this, you would create a method in the presenting view controller with the following signature:
- (IBAction)unwindAction:(UIStoryboardSegue*)unwindSegue;
This is different than a standard IBAction because the parameter type is a UIStoryboardSegue* instead of the normal id type (it doesn't have to be named unwindSegue:, it could be modalViewFinished: or whatever you like - the important part is that it has a return type of IBAction and a parameter type of UIStoryboardSegue*).
Once you have this method defined, in your storyboard you control-drag from the modal view controller icon (below its view, in the little bar of icons) and release the connection on the green exit sign. This will create an unwind segue, which you should give an identifier in the attributes inspector. Unwind segues will not show up visually in the storyboard canvas, so you will have to find it in the list of items on the left side of the canvas (this is collapsed by default, expand it by clicking the little circular button in the lower left hand corner of the canvas).
Once you've done that, rather than calling [self dismissViewControllerAnimated:YES completion:nil], just call [self performSegue:<Identifier you gave the unwind segue>] instead. During this process the unwindAction: method defined on the presenting view controller and the prepareForSegue: method on the modal view controller should be invoked. You can do whatever cleanup you need to do in these methods (calling the foo method from unwindSegue:, for example).
You call the method on the UIViewController that is your MainView, and pass it your UIViewController you want to be the ActionSheet.
UIActionSheet *actionSheetController =[[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"show otherview", nil];
[self presentModalViewController:actionSheetController animated:YES ];
To dismiss the UIActionSheet, dimissWithClickedButtonIndex:animated: is a method for the UIActionSheet that you can implement. The method can be called by whoever (so if you want to dismiss it from your mainview have a reference to the action sheet and do something like
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
{
switch (buttonIndex){
case 0:
{
[actionSheet dismissWithClickedButtonIndex:0 animated:YES];
}
break;
case 1:
{
MyClass *myclassObject = [[MyClass alloc]init];
[myclassObject foo];
}
}
}
The method is also called whenever the 'cancel' button is clicked by the user.
Use this ModalViewControllers link for better understanding...!

Modal Segue Chain

I have an iOS app that has a log in view (LognnViewController) and once a user is successfully authenticated they are taken to another view (DetailEntryViewController) to enter some simple details.
Once the details are entered the user is taken to the main part of the app that consists of a tab controller (TabViewController) that holds a variety of other views. The LogInViewController performs a modal segue to the DetailEntryViewController and the DetailEntryViewController then performs a modal segue to the TabViewController so I have kind of a modal segue chain going to get into the app. When a user logs out I want to go all the way back to the LogInViewController but when I do a:
[self.presentingViewController dismissModalViewControllerAnimated:YES];
...it pops the TabViewController and I end up back at the DetailEntryViewController instead of the first LogInViewController. Is there any way I can pop back to the first view controller easily or does doing this modal segue chain thing prevent me from that. I got the bright idea to put some code in the DetailEntryViewController viewWillAppear: that would automagically pop itself if the user had logged out but apparent making calls to dismiss a modal controller are not allowed in viewWillAppear: viewDidLoad:, etc.
Any ideas on how to make this happen?
I think this is not the best structure to implement your app. Modal controllers are supposed to be for temporary interruptions to the flow of the program, so using a modal to get to your main content is not ideal. The way I would do this is to make your tab bar controller the root view controller of the window, and then in the first tab's controller, present the login controller modally from the viewDidAppear method, so it will appear right away (you will briefly see the first tab's view unless you uncheck the "animates" box in the segue's attributes inspector). Present the details controller from that one, and then dismiss both modal controllers to get back to your main content. When the user logs out, just present that login controller again. I implement this idea like this. In the first tab's view controller:
- (void)viewDidLoad {
[super viewDidLoad];
_appStarting = YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (_appStarting) {
[self performSegueWithIdentifier:#"Login" sender:self];
_appStarting = NO;
}
}
Then in the last (second in your case) modal view controller, I have a button method:
-(IBAction)goBackToMain:(id)sender {
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
}
Figured it out myself...just had to go up one more level to get to the "root" view controller (LogInViewController) and found that this did the trick:
[[self.presentingViewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
As I said I'm just getting the presentingViewController (DetailEntryViewController) and then going up one more level and getting that controller's presenter (LogInViewController).
I had similar problem and my "modal segue chain" was not limited. I agree with the arguments in the answer and comments below about modal segues designed for different thing, but I liked the "horizontal flip" animation of modal segues and I couldn't find the easier way to replicate them... Also in general I don't see anything wrong in using things that were designed for one thing to achieve some other thing, like chaining modal controllers. Repeated "partial curl" animation can also apply to some scenario in some app.
So I implemented the stack of modal controllers as a property of controller:
#interface ModalViewController : UIViewController
#property (nonatomic, retain) NSMutableArray *modalControllers;
#end
When the first modal segue is executed the stack is created in prepareForSegue method of controller that is not modal:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"modalSegue"]) {
ModalViewController *controller =
(ModalViewController *)[segue destinationViewController];
controller.modalControllers = [NSMutableArray arrayWithObject: controller];
}
}
When one modal controller moves to another the destination is added to the stack (in the method of ModalViewCotroller)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"modalSegue"]) {
ModalViewController *destController =
(ModalViewController *)[segue destinationViewController];
// add destination controller to stack
destController.modalControllers = _modalControllers;
[destController.modalControllers addObject: destController];
}
}
To dismiss the whole stack at once was the most tricky part - you can't dismiss the previous controller before the next finished dismissing, so the cycle did not work, only recursive blocks did the trick, with avoiding the memory leak being the trickiest (I'm yet to check it, but I relied on this):
- (IBAction)dismissAllModalControllers: (id)sender
{
// recursive block that dismisses one auth controller
// all these dances are to avoid leaks with ARC
typedef void (^voidBlockType)();
__block void (^dismissController) ();
voidBlockType __weak dismissCopy = ^void(void) {
dismissController();
};
dismissController = ^void(void) {
int count = [_modalControllers count];
if (count > 0) {
// get last controller
UIViewController *controller =
(UIViewController *)[_modalControllers lastObject];
// remove last controller
[_modalControllers removeLastObject];
// dismiss last controller
[controller
// the first controller in chain is dismissed with animation
dismissViewControllerAnimated: count == 1 ? YES : NO
// on completion call the block that calls this block recursively
completion: dismissCopy];
}
};
// this call dismisses all modal controllers
dismissController();
}
[self.navigationController popToRootViewControllerAnimated:YES];

Resources