I have a view controller VC1 and I am doing following two operations on it:
Push another view controller VC2 from VC1's navigation controller by calling
[VC1.navigationController pushViewController: animated:YES];
Present another view controller VC3 from VC1 by calling.
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:VC3];
[VC1 presentViewController:navController animated:YES completion:nil];
Now when I am coming from VC2 to VC1 I am calling
[VC2.navigationController popViewControllerAnimated:YES];
and from VC3 to VC1 I am calling
[VC3.navigationController dismissViewControllerAnimated:YES completion:nil];
My question is when I am coming back to VC1 how do I know that whether I am coming from VC2(by dismissing) or VC3(by popping)?
Note: VC1 is loaded as child view controller inside a parent view controller VC4.
The best way would be to have the childViewController call back to the presenting view controller. By doing this the childViewController will not need to know the implementation details of whether it was presented modally or in a navigation stack etc.
With blocks it would look something like
#interface VC2 : UIViewController
#property (nonatomic, copy) void (^completion)(VC2 *viewController);
#end
You would set this block up something like this
VC2 *viewController = VC2.new;
viewController.completion = ^(VC2 *viewController) {
[viewController.navigationController popViewControllerAnimated:YES];
};
[VC1.navigationController pushViewController:viewController animated:YES];
Now where you was previously calling
[VC2.navigationController popViewControllerAnimated:YES];
You instead call
self.completion(self);
You put any logic you want to be associated with coming back from a specific viewController inside the completion handler
Related
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.
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];
I got first ViewController with out navigation controller, I go from it to second ViewController via
if (!self.mapViewController)
{
self.mapViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MapViewController"];
}
[self presentViewController:self.mapViewController animated:YES completion:nil];
I send some data with prepareForSegue to it, but I need that it also be with navigation controller.
I embed in a navigation controller in storyboard but my code still called second ViewController with out navigation.
I am not a professional with the way of the storyboard, however i believe that instead of presentViewController
you should be using the following function to present a storyboard VC based on segues.
[self performSegueWithIdentifier:#"SegueIdentifierHere" sender:nil];
Make sure that in your storyboard you have incorporated a UINavigationControllerVC as well.
Give an identifier to the navigation controller in storyboard. Instantiate and present that.
UINavigationController *navVC = [self.storyboard
instantiateViewControllerWithIdentifier:#"TheNavVCWhoseRootIsMyMapViewController"];
self.mapViewController = navVC.viewControllers[0]; // your map vc is at the root
[self presentViewController:navVC animated:YES completion:nil];
You are using presentViewController:animated:completion which will indeed display a UIViewController without the UINavigationController the previous UIViewController was embedded in.
Try using the following:
[self.navigationController pushViewController:self.mapViewController animated:YES]
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];
I'm trying to get working a simple operation. At least it seems simple. Ok, what I'd like to do is to push a view (with push view controller) from a view that has been pushed with modal view controller.
View1 --(push using modal view controller)-->View2--(push using push view controller)--View3.
Rigth now, i'm doing tests so i'm using a button to start the action. Here's the code I use to push from View2 to view 3:
//view2.h
UIToolbar *bar;
UIBarButtonItem *button;
UIToolbar *toolbar;
}
- (IBAction)demissModal:(id)sender;
- (IBAction)goView3:(id)sender;
#end
//view2.m
- (IBAction)goView3:(id)sender{
View3 *view_3 = [[View3 alloc] initWithNibName:#"View3" bundle:nil];
[self.navigationController pushViewController:view_3 animated:YES];
}
This is the same code I use to push View1 to View2, and it works. But when pushing View2 to View3, it's not working. Any idea of why happens that? Thanks!
View Controllers aren't actually 'modal' or 'push' view controllers. Modal or Push describe a transition between view controllers (called segues if you're using storyboards).
What I think you're asking is how to modally present a view controller, and then push another controller. The trick is when you modally present view controller #1, to actually present a navigation controller with its root view controller set as view controller #1.
MyViewController *myViewController = [MyViewController alloc] init];
UINavigationController *navController = [UINavigationController alloc] initWithRootViewController:myViewController];
// Presuming a view controller is asking for the modal transition in the first place.
[self presentViewController:navController animated:YES completion:nil];
// Now in myViewController, call [self.navigationController pushViewController:secondViewController animated:YES];
This is what it looks like using storyboards:
First of all, I'm not sure where that gegant_se is coming from.
Second of all, if you're pushing view2 from view1 the same way you're pushing view3 from view2, you're not using a modal.
Whenever you use a navigation controller to push a view controller, that view controller that was just pushed has a reference to the navigation controller, through the navigationController property. Try this:
[self.navigationController pushViewController:view_3 animated:YES];
Try this:
[self.navigationController pushViewController:view_3 animated:YES];
try this code AlarmList is view name .
AlarmListScreen *loscr=[[AlarmListScreen alloc]initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:loscr animated:YES];
[loscr release];