iOS - Semi-transparent modal view controller - ios

I want to present a view controller with a slightly transparent background modally over the current view, such that the first view is slightly visible under the modal view.
I set the alpha value of the modal view controller and set the modalPresentationStyle to UIModalPresentationCurrentContext, as suggested in another post.
The result is that the view background is transparent when animating up, but when view controller is in place it changes to opaque black. It goes back to being transparent while animating the dismissal.
How can I get it to be transparent when active ?
I have tested in iOS 6 and 7. The code I am using follows:
MyModalViewController *viewController = [[MyModalViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[navController setNavigationBarHidden:YES];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.navigationController presentViewController:navController animated:YES completion:NULL];

iOS 8 added a new modal presentation style specifically for this purpose:
presentedViewController.modalPresentationStyle = UIModalPresentationOverFullScreen
From the spec:
UIModalPresentationOverFullScreen
A view presentation style in which the presented view covers the screen. The views beneath the presented content are not removed from the view hierarchy when the presentation finishes. So if the presented view controller does not fill the screen with opaque content, the underlying content shows through.

If you are targeting ios 8 and above you can set the modal presentation style to "over current context" and you are done.
If ios 7 and below, you would have to create a custom transition style so that the presenting screen doesn't go blank after transition. That is rather complicated.
The solution I present offers a lot of flexibility: make a screenshot before showing the modal dialog and set that as the background image for the application window. By default, that background is black (that is what you see when the back view controller dissapears). Change the background to the screenshot of the app. Make the screenshot in the viewWillAppear or viewDidLoad method of your transparent view. This works even with push segues, not only modal dialogs, but you should avoid animations. In general, avoid animations which affect the position of the background view because those will make it seem like it snaps back into place when transition finishes. It is a good idea to reset the background to its previous black image on viewDidDissapear to avoid unwanted effects.
You can maintain a stack of such background images and you can do multiple "transparent" push seques. Or have some complex/deep menu which appears on top of some main screen. For these many reasons I think this solution is better than rolling your own transitioning code. It is more flexible and easier to implement, and you don't have to deal with the animations yourself.

The reason that the BG view controllers disappear after a modal is shown is that the default transition in iOS 7 removes the BG view after animation completed. If you define your own transition and you set your BG view not to be removed (just changing its alpha) then you will have the transparent modal view.

Same problem occured to me. I have solved it by looking at the following url about a custom alert controller. I managed to get it working even with a UINavigationController.
Swift
let viewController = UIViewController()
viewController.providesPresentationContextTransitionStyle = true
viewController.definesPresentationContext = true
viewController.modalPresentationStyle = .overCurrentContext
viewController.modalTransitionStyle = .crossDissolve
DispatchQueue.main.async {
self.navigationController?.present(viewController, animated: true, completion: nil)
}
Objective C
UIViewController *viewController = [UIViewController new];
viewController.providesPresentationContextTransitionStyle = true;
viewController.definesPresentationContext = true;
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
viewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentViewController:viewController animated:true completion:nil];
});

Here is a solution.
Create your presenting view controller. Add a backView to this view controller's main view. Name this as backView.
In SecondViewController.m
-(void)viewDidLoad
{
// Make the main view's background clear, the second view's background transparent.
self.view.backgroundColor = [UIColor clearColor];
UIView* backView = [[UIView alloc] initWithFrame:self.view.frame];
backView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
[self.view addSubview:backView];
}
Now you have a view controller with half transparent background. You can add anything you want to the self.view , the rest will be half transparent.
After that, in FirstViewController.m
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:secondViewController animated:YES completion:nil];

My solution is this:
Create a custom transparent overlay UIView that comes over any view, navigationbar and tabbbar.
-In the navigation controller (or tabbar controller) that your view controller is embedded in I create a custom view with it's frame equal to the frame of the navigation controller's view.
-Then I set it offscreen by setting it's origin.y to navigationController.view.height
-Then I create 2 functions -(void)showOverlay and -(void)hideOverlay that animate the overlay view on and off screen:
- (void)hideOverlay{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;//helpView is my overlay
frm.origin.y = self.offscreenOffset; //this is an Y offscreen usually self.view.height
self.helpView.frame = frm;
[UIView commitAnimations];
}
- (void)showOverlay{
[self.view bringSubviewToFront:self.helpView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;
frm.origin.y = self.onscreenOffset;
self.helpView.frame = frm;
[UIView commitAnimations];
}
-In my view controller I can just call
[(MyCustomNavCtrl *)self.navigationController showOverlay];
[(MyCustomNavCtrl *)self.navigationController hideOverlay];
And that's about it.

FYI: The syntax is now:
childVC.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen

Why don't you try setting this in AppDelegate
self.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
then changing the alpha on the view being presented

Related

Animating the presenting view in a UIPresentationController

For some context, I recommend reading this:
Very relevant question: "From View Controller" disappears using UIViewControllerContextTransitioning
Very relevant answer: https://stackoverflow.com/a/25901154/751268
I'm trying to implement a custom view controller transition that animates the new view controller to cover half the screen, while simultaneously shrinking the presenting view controller to 90% (centered in the window, underneath the presented view controller).
First, my problem was that viewFromKey: returned nil. To solve that, the answer mentioned:
If you want to animate the presenting view controllers's view you should consider using UIModalPresentationFullscreen style or continue using UIModalPresentationCustom and implement your own subclass of UIPresentationController with shouldRemovePresentersView returning YES.
I did that, and viewFromKey: doesn't return nil anymore, but now the presenting view controller disappears completely (which makes sense considering I explicitly say it should by implementing shouldRemovePresentersView).
I add the presenting view controller's view to the container view, but it still gets removed. Is there anything else I should be doing to get this working?
Here's some relevant code:
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
BOOL show = self.isPresentation;
UIView *menuView = show ? toView : fromView;
UIView *backView = show ? fromView : toView;
UIView *containerView = [transitionContext containerView];
[containerView addSubview:backView];
[containerView addSubview:dimmedView];
[containerView addSubview:menuView];
// Adjust transforms, alpha and perform animations
I thought that by returning YES from shouldRemovePresentersView and manually adding it to the containerView, that should fix the issue, but backView gets removed anyway...
I'm adding another answer, as my response is too long to fit in a comment.
First of all the viewForKey is available in iOS8, so unless you are targeting iOS8 only (why?) you should not use it, or use it after checking that the UIViewControllerContextTransitioning responds to that selector and use the viewControllerForKey for iOS7.
With that being said, it seems to me that this is a bug and I explain my self:
If you look at the UIPresentationController header file, you will see that it says
// Indicate whether the view controller's view we are transitioning from will be removed from the window in the end of the
// presentation transition
// (Default: YES)
- (BOOL)shouldRemovePresentersView;
So as you see the default is YES, so this should only overriden when you specifically want to say NO.
However, you are right, without this being set explicitly to YES, the viewForKey for the UITransitionContextFromViewControllerKey remains nil.
I think you should fill in a bug report for this and for now use the viewControllerForKey which is fine to be used (nothing wrong with this) as it's not deprecated and works in both OS versions without a problem.
And the reason this is most likely a bug is that the viewForKey should return a view for theUITransitionContextFromViewControllerKey when the shouldRemovePresentersView is explicitly set to NO and not YES.
My 2 cents
Why are you setting shouldRemovePresentersView to YES if what you want is that the fromView remains visible?
I had the same problem as you, as in my custom 3d presentation the parent view controller was removed as soon as the transition was completed.
The solution there is to change the modalPresentationStyle to UIModalPresentationOverCurrentContext
This will specifically prohibit the system to remove the owner viewController when the transition ends.
Still my 3D transition suffered from animation issues, when using this approach.
Si I ended up using a UIModalPresentationCustom modalPresentationStyle which solved almost all my issues, with the exception that the new view controller (which was a UINavigationController) would not move down when the in-call status bar would appear.
To solve this, I ended up changing the modalPresentationStyle back to UIModalPresentationFullScreen after the transition had been completed.
Here is my code that shows the new viewController with the custom presentation:
//show Login Screen
LoginViewController *viewController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
UINavigationController *loginNavController = [[UINavigationController alloc] initWithRootViewController:viewController];
loginNavController.interactivePopGestureRecognizer.enabled = YES;
loginNavController.transitioningDelegate = self.customTransitionDelegate;
loginNavController.modalPresentationStyle = UIModalPresentationCustom;
[navigationController presentViewController:loginNavController animated:YES
completion:^{
//important, else status bar is not moving entire screen down....
loginNavController.modalPresentationStyle = UIModalPresentationFullScreen;
}
];
Also very important is that you must NOT add the old view to the containerView when you run your dismissal animation
So if the animation is the presenting, add your toView to the containerView
UIView* inView = [transitionContext containerView];
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
.....
......
[inView insertSubview:toViewController.view aboveSubview:fromViewController.view];
But on the dismissal presentation DO NOT ADD the view to the containerView as it is still showing (since we specifically asked the system NOT to remove it), but simply animate between the two views.

Custom presented UIViewController changing to fullscreen

I have a view controller presented using UIModalPresentationCustom presentation style. I use a custom UIViewControllerTransitioningDelegate to present the view controller as a sidebar (so it slides in from the edge of the screen and does not occupy the full screen).
However when I then present another view controller from this one using UIModalPresentationFullScreen — and then dismiss the full screen view controller, my underlying custom presented controller is suddenly resized to occupy the full screen. Does anyone know why this is the case?
Edit: this is essentially my animateTransition method for presenting the sidebar — I've stripped out most of the code to make it readable. Basically it gets the container from the transitionContext, adds and animates the destination view controller's view to the container.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *container = transitionContext.containerView;
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
if( toVC.isBeingPresented )
{
[container addSubview:toView];
//... Animate some new frame for toView
//Call [transitionContext completeTransition:YES] on animation completion
}
else
{
//... Animate fromView out
//On completion remove fromView from superview
//Call [transitionContext completeTransition:YES] on animation completion
}
}
Edit 2: Doing a little more research, I notice that the frame of my custom presented view controller's view is being set when the view controller above it in the modal stack is dismissed. The following stack trace leads to the frame being set as full screen:
0 -[MyCustomPresentedViewControllerView setFrame:]
1 -[UIView(MPAdditions) setFrameOrigin:]
2 -[UIViewControllerAccessibility(SafeCategory) dismissViewControllerWithTransition:completion:]
3 -[UIViewController dismissViewControllerAnimated:completion:]
Changing the presentation style by assigning:
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
results in the modal presentation controller occupying a fraction of the screen, the same fraction as the original presented view controller of the UIPresentationController.
However, using viewController.modalPresentationStyle = UIModalPresentationOverFullScreen
is better for this case, since his modal controller is full screen.
I am experiencing the same issue. I've tried debugging it using symbolic breakpoints and there seem to be some internal call on some kind of layout manager that does this.
While I wasn't able to "solve" this (it seems to me like a bug in the SDK), I was able to come up with a workaround that fixes this. Basically, you have to set the correct dimensions of the presented view at two opportune times. Like this:
In the view controller that was presented using your custom UIPresentationController, add this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
dispatch_async(dispatch_get_main_queue(), ^{
self.view.frame = [self.presentationController frameOfPresentedViewInContainerView];
});
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.frame = [self.presentationController frameOfPresentedViewInContainerView];
}
And if you are wondering: yes, you do need it to do in two places. Because weirdly enough, when I did it only in the viewDidAppear async block, it got broken (resized to fullscreen) once the animation finished.
Try to use:
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
It helped me in iOS 8.1

Why isn't the rootViewController of my modally presented (form sheet) navController aware of it's smaller size when presented modally?

I am working on an iPad app with a few different modal views, and this code is pretty common:
UIViewController *v1 = [[UIViewController alloc] init];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:v1];
nav1.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:nav1 animated:YES completion:nil];
It could be that I am doing this wrong, but this is how I am presenting a navController-nested vc modally.
The problem is that within the v1 class, any reference to self.frame/bounds results in full screen dimensions:768x1024. Even though the navController clearly isn't being displayed with that size.
What should I be doing to make it so that the v1 vc knows how big it actually is? So that if I wanted to add, say, a tableView, it would know how big it should be?
Thanks!
EDIT:
I have tried a few more things, and still don't have a solution to this problem. I have made a simple sample project to illustrate the problem I am having. I just have one view and this is the core of the code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Frame: %#", NSStringFromCGRect(self.view.frame));
NSLog(#"Bounds: %#", NSStringFromCGRect(self.view.bounds));
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(self.view.frame.size.width - 400, 0, 400, 400);
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:#selector(presentModal) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)presentModal {
SSViewController *view = [[SSViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view];
nav.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:nav animated:YES completion:nil];
}
When this view loads, I have a big red button that is up against the top right corner of my view. When I press the button, it loads the same VC in a modal view embedded in a navController. The button shows up nearly off screen because the frame hasn't changed. It still shows as full screen. Here is a link to the project.
Not sure why you're having the issue you're having. I'm using the following code:
- (void)presentNewView {
NewViewController *newVC = [[NewViewController alloc] initWithNibName:nil bundle:nil];
newVC.view.backgroundColor = [UIColor redColor];
UINavigationController *newNC = [[UINavigationController alloc] initWithRootViewController:newVC];
newNC.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:newNC animated:YES completion:NULL];
}
.. it results in the following in the simulator:
.. and when I print out the first ViewController's frame and bounds (I thought it might be an issue with the two) I get the following:
frame height: 1024.000000
frame width: 768.000000
bounds height: 1024.000000
bounds width: 768.000000
.. and when I print out the presented ViewController's frame/bounds I get the following:
frame height: 620.000000
frame width: 540.000000
bounds height: 620.000000
bounds width: 540.000000
How are you determining the size of the frame exactly? Any reference within the v1 class that was presented modally SHOULD know its actual size, like I showed above.
EDIT
The major difference I found with my code and yours, is that in my code I created a subclass of my view controller "NewViewController" and was printing out the frame from within that class. The class itself seems to be aware of its correct bounds, but the class the presented it seems not to be. This is demonstrated by printing the view property from the ViewController class that presented it:
NewViewController's View From Presenting Class: frame = (0 0; 768 1024)
..compared to printing out the self.view from within the ViewDidAppear method of NewViewController itself:
NewViewController's View Did Appear: frame = (0 0; 540 576)
Moral of the story, if you are going to be presenting a UIViewController in the way you've shown, you're likely going to want to subclass UIViewController anyway so you can customize it however you want, so within that file if you reference self.view or self.bounds you will be getting the ACTUAL view/bounds.
EDIT #2
Based on the project you provided, the reason why you are having that issue is because you are printing out the frame/bounds of the view in viewDidLoad as opposed to viewDid/viewWillAppear. Adding those NSLog statements to VWA or VDA provides you the correct frame, so as I said in my initial edit, you should be fine accessing the view of the modal correctly at that point.
It's a new feature of iOS7. If you embed a UIViewController in navigation bar, it won't get smaller, because by default navigation bar is translucent.
You will see it if you change the background color of a view controller, that the top part of it is actually behind the navigation bar.
To lay out the v1 view controller underneath the navigation bar, you can use following code:
if ([v1 respondsToSelector:#selector(edgesForExtendedLayout)]) {
v1.edgesForExtendedLayout = UIRectEdgeNone;
}
It will behave just as in iOS6.
when presenting view controllers modally from a child view controller (one that has lass than the full screen and is a child of another view controller..) it is important to do this so that the modal controller knows the size of the canvas its appearing in
childViewController.definesPresentationContext = YES;
modalViewControllerWhichIsAboutToBePushed.modalPresentationStyle = UIModalPresentationCurrentContext

How to keep background transparent when presentViewController in iPhone?

I want to keep background transparent like UIActivityController in iPhone iOS6
I try to clear color and opaque but background always black like this (iPhone iOS5 simulator)
This is presentViewController code:
ShareViewController *controller = [[ShareViewController alloc] initWithNibName:#"ShareViewController_iPhone" bundle:nil];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:^{
NSLog(#"Activity complete");
}];
Please help! Thanks!
See what you have to do is add a UIView behind the custom UIActionsheet you mentioned. Here what you can do is keep the Background-color of the UIView as white and then keep its alpha = 0.5.
When you dismiss the UIActionsheet you can removeFromSuperview the UIView added behind too.
Instead of presentViewController use [UIView transitionWithView] to define your custom animation.
Start by having your view controller retained somewhere. (If you are using ARC have it as property on the parent view controller).
Add the view controller's view as a subview to the current VC's view. Set the frame to be CGRectMake(0, screenBottom, screenWidth, screenHeight / 2);
Animate by changing the frame so that it slides onto the screen.
[UIView transitionWithView:<#(UIView *)view#> duration:0.5 options:nil
animations:^
{
view.frame = CGRectMake(0, screenHeight / 2, screenWidth, screenHeight/ 2);
} completion:nil];

Display Modal View Controller over detail view in UISplitViewController

I have a UISplitViewController in an iPad app. When something is selected from the table I want to fade in a modal view controller over the detail view. I can present it without a problem, but for some reason I can't get it to match the frame of the detail view. I would like it to stick to the detail view controller frame on rotation as well. Does anyone have any experience with this? This is my code to display. The detail view controller reference is set in the app delegate and passed down the table controllers.
QuestionViewController_iPad *questionView = [[[QuestionViewController_iPad alloc] initWithNibName:#"QuestionViewController_iPad" bundle:nil] autorelease];
questionView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
// Not quite
questionView.modalPresentationStyle = UIModalPresentationCurrentContext;
questionView.questionQuizCon = [QuestionQuizConnection firstQuestionForQuiz:quizCatCon.quiz];
// Maybe something like this?
[self.detailViewController presentModalViewController:questionView animated:YES];
When the modal view presents, it matches the size of the detail view controller, but it doesn't but it sits on the top left of the screen behind the master view controller. It also doesn't resize on rotation. I have the springs and struts set to auto size and fill. The height changes on rotation but it won't fill the width.
I couldn't get this to look right any way I tried it so I ended up just using view transitions to make it look like pages being torn off a notebook. That looks better anyway.
// Transition the view on as a subview.
[UIView transitionWithView:self.detailViewController.pageView duration:1.0
options:UIViewAnimationOptionTransitionCurlUp
animations:^ {
questionView.view.frame = CGRectMake(0, 0, self.detailViewController.pageView.frame.size.width, self.detailViewController.pageView.frame.size.height);
[self.detailViewController.pageView addSubview:questionView.view];
// Watch this one
self.detailViewController.currentQuestionViewController = questionView;
}
completion:nil];
After [self.detailViewController presentModalViewController:questionView animated:YES]; you should set center property and/or frame of questionView. Be sure that you set it after presenting modal view.

Resources