Present multiple modal view controllers? - ios

update:
I've faced this problem again, and found another way. If presenting controller is not embedded in navigation controller, it will be hidden if presented controller is not fullscreen and will became black. Method setModalPresentationStyle:UIModalPresentationCurrentContext can be applied only to navigation controller. So embed presenting controller in UINavigationController, set UIModalPresentationCurrentContext to it and present new controller - you will get dialog controller.
I'm presenting search controller, it have tableView that push on stack detailed controller.
Detailed controller can present view controller with message, it consists from small UIView and semitransparent background.
Problem: when last view controller presented, all view controllers under it becomes hidden and controller, that presented search controller becomes visible.
Here what I'm doing:
SearchViewController *viewController = [[SearchViewController alloc] initWithNibName:#"SearchViewController" bundle:nil];
viewController.data = dataArray;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[self.navigationController setModalPresentationStyle:UIModalPresentationCurrentContext];
[self.navigationController presentViewController:navigationController animated:YES completion:nil];
than table pushes detailed view:
DetailViewController *viewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
[viewController setHidesBottomBarWhenPushed:YES];
viewController.dataItem = [data objectAtIndex:(NSUInteger) [indexPath row]];
[self.navigationController pushViewController:viewController animated:YES];
and detailed view presenting message box:
MessageController *controller = [[MessageController alloc] initWithNibName:#"MessageController" bundle:nil];
controller.message = message;
[self presentViewController:controller animated:YES completion:nil];
When it's dismissed, all controllers under it became visible.
update:
all I wanted is to present modally a view controller that will have uitableview. From this table to show detailed view that will be able to show message box. Message box must be another view controller. And when message box is shown all two preceding controllers disappears. that is the issue.

In some situations, you could have each modally presented view controller present the next one.
let window = UIApplication.sharedApplication().keyWindow!
if let modalVC = window.rootViewController?.presentedViewController {
modalVC.presentViewController(vc, animated: true, completion: nil)
} else {
window.rootViewController!.presentViewController(vc, animated: true, completion: nil)
}

Dismiss all view controllers above the presented view controller.
Apple's documentation follows:
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.
Dismiss two modal view controllers.
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#jumpTo_51

This answer might seem a little unconventional, but it works. It lets you "open multiple modals", but only show one at a time. The key is to have just one modal, but use a Tab Bar Controller to switch between View Controllers.
Wire your modal segue from the blue (presenting) View Controller to a modal container (which is just a regular View Controller)
The modal container holds just a Container View, whose sole purpose is to embed a Tab Bar Controller.
Embed a Tab Bar Controller inside the Container View
Each tab can be a separate View Controller, representing "multiple modals"
Set the Tab Bar Controller's Tab Bar to hidden
Switch between modals using tabBarController?.selectedIndex = 1
Close the modal normally with dismissViewControllerAnimated(true, completion: nil)
It would look something like this:

The trivial way to attempt to achieve that is to just create the VCs
you want to present modally and present one after the other.
Unfortunately, this is not gonna work. What you get is just the first
VC presented, all others just go nowhere. UIKit just won’t cooperate
with you here.
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.view.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Set the correct modalPresentationStyle on both the navigationController AND the viewController itself.
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
vcToPresent.modalPresentationStyle = UIModalPresentationFormSheet;
vcToPresent.preferredContentSize = CGSizeMake(650, 450);
[self presentViewController:vcToPresent animated:YES completion:^{
self.navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
}];`

I found a solution: provide your own custom animation:
let transition: CATransition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionReveal
transition.subtype = kCATransitionFromBottom
self.view.window!.layer.add(transition, forKey: nil)
self.presentingViewController?.presentingViewController?.dismiss(animated: false, completion: nil)

Related

viewWillAppear of parent triggered when second presented viewcontroller dismissed

I have 3 viewcontrollers
let's say AVC, BVC and CVC
First I present BVC from AVC and then present CVC from BVC
So the hierarchy would be AVC->BVC->CVC.
When I dismiss CVC from BVC, viewWillAppear of AVC is triggered, how can I prevent viewWillAppear of AVC getting called?
Thanks
UPDATE
First present (in AVC)
BTViewController *BTVC = [self.storyboard instantiateViewControllerWithIdentifier:#"BTVCIns"];
__weak BTViewController *weakBTVC = BTVC;
BTVC.dismissAction = ^{[weakBuyTicketVC dismissViewControllerAnimated:YES completion:nil];};
[BTVC setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self presentViewController:BTVC animated:YES completion:nil];
Second present (in BVC)
PMViewController *PMVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PMVCIns"];
__block PMViewController *blockPMVC = PMVC;
blockPMVC.dismissAction = ^{[blockPMVC dismissViewControllerAnimated:YES completion:nil];};
blockPMVC.delegate = self;
[self presentViewController:PMVC animated:YES completion:nil];
From the View Controller Programming Guide for iOS
When presenting a view controller using the UIModalPresentationFullScreen style, UIKit normally removes the views of the underlying view controller after the transition animations finish. You can prevent the removal of those views by specifying the UIModalPresentationOverCurrentContext style instead. You might use that style when the presented view controller has transparent areas that let underlying content show through.
Since you are using UIModalPresentationOverCurrentContext, iOS considers that BVC may have transparent areas which expose AVC, hence when BVC appears it is possible that AVC is also going to appear, so viewWillAppear is called.

Pop SWRevealViewController

I'm using SWRevealViewController to build a sliding menu. Before accessing the menu, a login view is displayed to allow the user login. Now i want to let the user disconnect and display the login view again. My question is how to pop the SWRevealViewController.
code that provides access to application after login is:
MainViewController *vMainMenu = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
vMainMenu.strURL = URL;
LeftMenu *vLeftMenu = [[LeftMenu alloc] initWithNibName:#"LeftMenu" bundle:nil];
UINavigationController *principalNavController = [[UINavigationController alloc] initWithRootViewController:vMainMenu];
UINavigationController *rearNavController = [[UINavigationController alloc] initWithRootViewController:vLeftMenu];
SWRevealViewController *mainRevealController = [[SWRevealViewController alloc] initWithRearViewController:rearNavController frontViewController:principalNavController];
mainRevealController.delegate = self;
self.viewController = mainRevealController;
principalNavController.navigationBar.hidden = YES;
rearNavController.navigationBar.hidden = YES;
self.window.rootViewController = self.viewController;
This solution is not working:
[self.revealViewController.navigationController popViewControllerAnimated:YES];
Splash *vSplash = [[Splash alloc] initWithNibName:#"Splash" bundle:nil];
[self.navigationController pushViewController:vSplash animated:YES];
Can you help me please
Thank you.
You can't go back to your LoginViewController by pop. You can add PresentViewController without animation to do this.
Splash *vSplash = [[Splash alloc] initWithNibName:#"Splash" bundle:nil];
[self presentViewController: vSplash animated:NO completion:nil];
I had the same issue. I have implemented several storyboards.
let storyboard = UIStoryboard(name: "Login", bundle: nil)
let startcontroller = storyboard.instantiateViewControllerWithIdentifier("LoginVC") as UIViewController
window?.rootViewController = startcontroller
With this snippet you can access the right View Controller.
You can use an "Unwinding Segue". The unwinding segue is like a regular segue which goes back instead of forward.
First, go to the Login View Controller and add the following method:
-(IBAction)unwindToLoginScreen:(UIStoryboardSegue *)segue {
}
The method may be empty and it can have any name; it is good to use descriptive names because you might have many unwind segues on your code and it will be useful to be able to distinguish between them.
This unwind method will act as a global constant that will be visible to all View Controllers on the storyboard.
Now, on each view controller that should be able to return to the Login Screen you create a Unwind Segue. You can do this on the Storyboard:
Select the View Controller you want; on the top bar you should see three buttons, one representing the View Controller, another representing the First Responder and another representing an Exit.
Ctrl+Drag from the View Controller button to the Exit button. A context menu will appear and will list all visible unwind methods you created on your storyboard.
On the Document Outline, an "Unwind segue" will appear for the view controller. You can add an identifier to that segue (such as "Login" in your case). Then you call performSegueWithIdentifier. Select the one you created on your Login view controller.
Doing this, the complete stack between the view controller and the login view controller will be unwinded. That is, all view controllers stacked above the Login view controller will be popped.
You can also use prepareForSegue for the unwind segues the same way you would use them for regular segues. And you can add code to the -(IBAction) method if you want to do something as a result to the segue.

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

Navigation Controller from a View Controller

I'm using .xib files in an app. And I want to have two view controllers normal way and from the third view controller, embed it in a navigation view controller. Here's an illustration of how I want it.
I know I can embed a navigation controller in a modally presented view controller like this.
let firstVC = FirstViewController(nibName: firstViewController, bundle: nil)
let navController = UINavigationController(rootViewController: firstVC)
presentViewController(navController, animated: true, completion: nil)
But if I embed it in a navigation controller and push it, the app crashes with the error Pushing the same view controller instance more than once is not supported.
let firstVC = FirstViewController(nibName: firstViewController, bundle: nil)
let navController = UINavigationController(rootViewController: firstVC)
navController.pushViewController(firstVC, animated: true)
Is it possible to do this at all? If so, can someone please explain how?
Thank you.
NOTE: Don't confuse the code snippets with the diagram above. The firstViewController in the code is not the first view controller in the diagram.
Suppose that FirstViewcontroller,Secondviewcontroller,Thirdviewcontroller are the three view controllers. then the transition from second to third view controller use the code given below.
Secondviewcontroller *second=[[Secondviewcontroller alloc]init];
UINavigationController *nav=[[UINavigationController alloc]initWithRootViewController: second];
[self presentViewController:nav animated:NO completion:nil];
use the first view controller as the root view controller of the UINavigationController
use the following method to present your next view controller
[self.navigationController pushViewController:vc animated: YES];
in the viewWillAppear method, if you wanna hide the navigation bar, use
[self.navigationController setNavigationBarHidden:YES animated:animated];
If you wanna show the navigation bar, use
[self.navigationController setNavigationBarHidden:NO animated:animated];

IOS 7 : Adding modal ViewController with transparency : Custom Size

What I want to do is present a viewController that comes from the bottom and has a custom size. Whatever I seem to do either looks naff (e.g its animated up, flashes then goes transparent), is fullscreen or blacks out the whole screen. I have read a few solutions with custom delegates handing the animation but there must be a simple way. Also a lot of the solutions are for iOS 6 <
HackViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"HackViewController"];
vc.delegate = self;
vc.modalPresentationStyle= UIModalPresentationCustom;
vc.view.frame = CGRectMake(0, 0, 800, 400);
vc.credits = CREDIT_PER_LEVEL * building.buildingLevels;
vc.view.backgroundColor = [UIColor clearColor];
[self presentViewController:vc animated:YES completion:^{
vc.view.backgroundColor = [UIColor clearColor];
}];
If you wanna add viewController (say VC2) as a modal over first viewController(say VC1), then in VC1, when you are presenting VC2,
[self presentViewController:VC2 animated:YES completion:nil];
In VC2 viewDidLoad
[self.view setBackgroundColor:[UIColor colorWithRed:211.0/255.0 green:211.0/255.0 blue:211.0/255.0 alpha:0.8]];
And, in AppDelegate under application didFinishLaunchingWithOptions:
[[self.window rootViewController] setModalPresentationStyle:UIModalPresentationCurrentContext];
You can create other views like imageView or label, etc in VC2
If you only want to support iOS7 and above, you can implement this using a custom modal presentation style (UIModalPresentationCustom) and implement the transitioningDelegate to return a transitioning controller. In the transitioning controller, you will be called when the presentation is to happen, and you can provide the target frame of the presented view controller. This has a big advantage of using the system presentation model, and the system is aware a controller has been presented.
If you need to support iOS6, you need to add the presented view controller's as a subview, the view controller as a child view controller of the presenting view controller, and manager the view hierarchy yourself. This is less than optimal because of manual view hierarchy control and child/parent view controllers.
You don't have to presentViewController:, you can do that with addSubview:
Keep doing what you are doing but add view of vc to view of presented view controller instead of presenting vc with presentViewController:
If you do that within an animation block, it would look nice.

Resources