How to use presentModalViewController to create a transparent view - ios

I am displaying a modal view with
[self presentModalViewController:controller animated:YES];
When the view moves up the screen it is transparent as per the setting in the xib file it is created from, but once it fills the screen it goes opaque.
Is there anyway of keeping the view transparent?
I suspect that the view it is being placed over is not being rendered rather then that the modal view is becoming opaque.

After iOS 3.2 there is a method to do this without any “tricks” – see the documentation for the modalPresentationStyle property. You have a rootViewController which will present the viewController.
So here's the code to success:
viewController.view.backgroundColor = [UIColor clearColor];
rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[rootViewController presentModalViewController:viewController animated:YES];
With this method the viewController's background will be transparent and the underlying rootViewController will be visible. Please note that this only seems to work on the iPad, see comments below.

Your view is still transparent, but once your modal controller is at the top of the stack, the view behind it is hidden (as is the case with any top-most view controller). The solution is to manually animate a view yourself; then the behind-viewController won't be hidden (since you won't have 'left' it).

What I needed to get this to work:
self.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;

For those who want to see some code, here's what I added to my transparent view's controller:
// Add this view to superview, and slide it in from the bottom
- (void)presentWithSuperview:(UIView *)superview {
// Set initial location at bottom of superview
CGRect frame = self.view.frame;
frame.origin = CGPointMake(0.0, superview.bounds.size.height);
self.view.frame = frame;
[superview addSubview:self.view];
// Animate to new location
[UIView beginAnimations:#"presentWithSuperview" context:nil];
frame.origin = CGPointZero;
self.view.frame = frame;
[UIView commitAnimations];
}
// Method called when removeFromSuperviewWithAnimation's animation completes
- (void)animationDidStop:(NSString *)animationID
finished:(NSNumber *)finished
context:(void *)context {
if ([animationID isEqualToString:#"removeFromSuperviewWithAnimation"]) {
[self.view removeFromSuperview];
}
}
// Slide this view to bottom of superview, then remove from superview
- (void)removeFromSuperviewWithAnimation {
[UIView beginAnimations:#"removeFromSuperviewWithAnimation" context:nil];
// Set delegate and selector to remove from superview when animation completes
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
// Move this view to bottom of superview
CGRect frame = self.view.frame;
frame.origin = CGPointMake(0.0, self.view.superview.bounds.size.height);
self.view.frame = frame;
[UIView commitAnimations];
}

The Apple-approved way to do this in iOS 8 is to set the modalPresentationStyle to 'UIModalPresentationOverCurrentContext'.
From the UIViewController documentation:
UIModalPresentationOverCurrentContext
A presentation style where the content is displayed over only the
parent view controller’s content. 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.
When presenting a view controller in a popover, this presentation
style is supported only if the transition style is
UIModalTransitionStyleCoverVertical. Attempting to use a different
transition style triggers an exception. However, you may use other
transition styles (except the partial curl transition) if the parent
view controller is not in a popover.
Available in iOS 8.0 and later.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/
The 'View Controller Advancements in iOS 8' video from WWDC 2014 goes into this in some detail.
Be sure to give your presented view controller a clear background color (otherwise, it will still appear opaque).

There is another option: before showing the modal controller, capture a screenshot of the whole window. Insert the captured image into an UIImageView and add the image view to the controller's view you're about to show.
Then send to back.
Insert another view above the image view (background black, alpha 0.7).
Show your modal controller and it looks like it was transparent.
Just tried it on iPhone 4 running iOS 4.3.1. Like charm.

this is quite old, but i solved the same problem as follows:
Since i need to present a navigation controller in iPhone, adding a subview wasn't a viable solution.
So what i did:
1) Before presenting the view controller, take a screenshot of your current screen:
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, self.view.opaque, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
2) Create the view controller you want to present, and add the background as a subview, sending it to back.
UIViewController * presentingVC = [UIViewController new];
UIImageView * backgroundImageOfPreviousScreen = [[UIImageView alloc] initWithImage:backgroundImage];
[presentingVC.view addSubview:backgroundImageOfPreviousScreen];
[presentingVC.view sendSubviewToBack:backgroundImageOfPreviousScreen];
3) Present your view controller, but before that in the new view controller, add a transparent view in the viewDidLoad (i used ILTranslucentView)
-(void)viewDidLoad
{
[super viewDidLoad];
ILTranslucentView * translucentView = [[ILTranslucentView alloc] initWithFrame:self.view.frame];
[self.view addSubview:translucentView];
[self.view sendSubviewToBack:translucentView];
}
And that's all!

I wrote down my findings about this in a different question, but the gist of it is that you have to call modalPresentationStyle = UIModalPresentationCurrentContext on whatever owns the full screen at the moment. Most of the time, it's whatever is the [UIApplication sharedApplication].delegate.window's rootViewController. It could also be a new UIViewController that was presented with modalPresentationStyle = UIModalPresentationFullScreen.
Please read my other much more detailed post if you're wondering how I specifically solved this problem. Good luck!

This appears to be broken in IOS 8, I am using a navigation controller and the context that is being displayed is the Navigation menus context which in our case is a sliding Menu controller.
We are using pod 'TWTSideMenuViewController', '0.3' have not checked to see if this is an issue with the library yet or the method described above.

This worked to me in iOS 8-9:
1- Set your view controller's background with an alpha
2- add this code:
TranslucentViewController *tvc = [[TranslucentViewController alloc] init];
self.providesPresentationContextTransitionStyle = YES;
self.definesPresentationContext = YES;
[tvc setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self.navigationController presentViewController:tvc animated:YES completion:nil];

I know this is pretty old question. I was stuck on this issue and I was able to get a lead from this thread. So putting here how I got it worked :) .
I am using storyboard and I have segue to the ViewController which is to be presented. The view controller have a transparent background colour. Now in the Attributes inspector of the segue I set the presentation to "Over current context".And it worked for me. I am developing for iPhone.

I've created open soruce library MZFormSheetController to present modal form sheet on additional UIWindow. You can use it to present transparency modal view controller, even adjust the size of the presented view controller.

In my condition i am having view on same viewController. So make a new view controller for holding UIView. Make that view transparent by setting it's alpha property.
Then on a button click i wrote this code. It looks good.
UIGraphicsBeginImageContext(objAppDelegate.window.frame.size);
[objAppDelegate.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIViewController *controllerForBlackTransparentView=[[[UIViewController alloc] init] autorelease];
[controllerForBlackTransparentView setView:viewForProfanity];
UIImageView *imageForBackgroundView=[[UIImageView alloc] initWithFrame:CGRectMake(0, -20, 320, 480)];
[imageForBackgroundView setImage:viewImage];
[viewForProfanity insertSubview:imageForBackgroundView atIndex:0];
[self.navigationController presentModalViewController:controllerForBlackTransparentView animated:YES];
And it shows what i want. hope it help some one.

Here's a category I've created that will solve the problem.
//
// UIViewController+Alerts.h
//
#import <UIKit/UIKit.h>
#interface UIViewController (Alerts)
- (void)presentAlertViewController:(UIViewController *)alertViewController animated:(BOOL)animated;
- (void)dismissAlertViewControllerAnimated:(BOOL)animated;
#end
//
// UIViewController+Alerts.m
//
#import "UIViewController+Alerts.h"
#implementation UIViewController (Alerts)
- (void)presentAlertViewController:(UIViewController *)alertViewController animated:(BOOL)animated
{
// Setup frame of alert view we're about to display to just off the bottom of the view
[alertViewController.view setFrame:CGRectMake(0, self.view.frame.size.height, alertViewController.view.frame.size.width, alertViewController.view.frame.size.height)];
// Tag this view so we can find it again later to dismiss
alertViewController.view.tag = 253;
// Add new view to our view stack
[self.view addSubview:alertViewController.view];
// animate into position
[UIView animateWithDuration:(animated ? 0.5 : 0.0) animations:^{
[alertViewController.view setFrame:CGRectMake(0, (self.view.frame.size.height - alertViewController.view.frame.size.height) / 2, alertViewController.view.frame.size.width, alertViewController.view.frame.size.height)];
}];
}
- (void)dismissAlertViewControllerAnimated:(BOOL)animated
{
UIView *alertView = nil;
// find our tagged view
for (UIView *tempView in self.view.subviews)
{
if (tempView.tag == 253)
{
alertView = tempView;
break;
}
}
if (alertView)
{
// clear tag
alertView.tag = 0;
// animate out of position
[UIView animateWithDuration:(animated ? 0.5 : 0.0) animations:^{
[alertView setFrame:CGRectMake(0, self.view.frame.size.height, alertView.frame.size.width, alertView.frame.size.height)];
}];
}
}
#end

After a lot of research looks like this will solve our issue and serve our purpose.
create a segue from source VC to destination VC with an identifier.
for example "goToDestinationViewController"
okay to makes lives easy let's consider the current view controller i.e, the one you want behind your transparent view as source and the destination as destination
Now in source VC in viewDidLoad: or view
performSegueWithIdentifier("goToDestinationViewController", sender: nil)
good we are half way through.
Now go to your storyboard. Click on the segue. which should look like this:
segue
change the options to what are shown.
Now comes the real solution.
in your destination view controller's viewDidLoad add this code.
self.modalPresentationStyle = .Custom
.........................................................................THAT EASY..................................................................

Alternate way is to use a "container view". Just make alpha below 1 and embed with seque.
XCode 5, target iOS7.
can't show image, not enough reputation)))
Container view available from iOS6.

This code works fine on iPhone under iOS6 and iOS7:
presentedVC.view.backgroundColor = YOUR_COLOR; // can be with 'alpha'
presentingVC.modalPresentationStyle = UIModalPresentationCurrentContext;
[presentingVC presentViewController:presentedVC animated:YES completion:NULL];
But along this way you loose 'slide-from-the-bottom' animation.

I found this elegant and simple solution for iOS 7 and above!
For iOS 8 Apple added UIModalPresentationOverCurrentContext, but it does not work for iOS 7 and prior, so I could not use it for my case.
Please, create the category and put the following code.
.h file
typedef void(^DismissBlock)(void);
#interface UIViewController (Ext)
- (DismissBlock)presentController:(UIViewController *)controller
withBackgroundColor:(UIColor *)color
andAlpha:(CGFloat)alpha
presentCompletion:(void(^)(void))presentCompletion;
#end
.m file
#import "UIViewController+Ext.h"
#implementation UIViewController (Ext)
- (DismissBlock)presentController:(UIViewController *)controller
withBackgroundColor:(UIColor *)color
andAlpha:(CGFloat)alpha
presentCompletion:(void(^)(void))presentCompletion
{
controller.modalPresentationStyle = UIModalPresentationCustom;
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
__block UIView *overlay = [[UIView alloc] initWithFrame:keyWindow.bounds];
if (color == nil) {
color = [UIColor blackColor];
}
overlay.backgroundColor = color;
overlay.alpha = alpha;
if (self.navigationController != nil) {
[self.navigationController.view addSubview:overlay];
}
else if (self.tabBarController != nil) {
[self.tabBarController.view addSubview:overlay];
}
else {
[self.view addSubview:overlay];
}
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:controller
animated:true
completion:presentCompletion];
DismissBlock dismissBlock = ^(void) {
[self dismissViewControllerAnimated:YES completion:nil];
[UIView animateWithDuration:0.25
animations:^{
overlay.alpha = 0;
} completion:^(BOOL finished) {
[overlay removeFromSuperview];
}];
};
return dismissBlock;
}
#end
Note: it works also for navigationContoller, tabBarController.
Example of usage:
// Please, insure that your controller has clear background
ViewController *controller = [ViewController instance];
__block DismissBlock dismissBlock = [self presentController:controller
withBackgroundColor:[UIColor blackColor]
andAlpha:0.5
presentCompletion:nil];
// Supposed to be your controller's closing callback
controller.dismissed = ^(void) {
dismissBlock();
};
Enjoy it! and please, leave some feedbacks.

This is the best and cleanest way I found so far:
#protocol EditLoginDelegate <NSObject>
- (void)dissmissEditLogin;
#end
- (IBAction)showtTransparentView:(id)sender {
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"foo bar"
delegate:self
cancelButtonTitle:#"cancel"
destructiveButtonTitle:#"destructive"
otherButtonTitles:#"ok", nil];
[actionSheet showInView:self.view];
}
- (void)willPresentActionSheet:(UIActionSheet *)actionSheet{
UIStoryboard *loginStoryboard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
self.editLoginViewController = [loginStoryboard instantiateViewControllerWithIdentifier:#"EditLoginViewController"];
self.editLoginViewController.delegate = self;
[self.editLoginViewController viewWillAppear:NO];
[actionSheet addSubview:self.editLoginViewController.view];
[self.editLoginViewController viewDidAppear:NO];
}

The best solution I have come across is to use the addChildViewController method.
Here is an excellent example : Add a child view controller's view to a subview of the parent view controller

I try to use multiple methods to solve but still failed, the follow code implemented finally.
The resolution by Swift:
// A.swift init method
modalPresentationStyle = .currentContext // or overCurrentContent
modalTransitionStyle = .crossDissolve // dissolve means overlay
then in B view controller:
// B.swift
let a = A()
self.present(a, animated: true, completion: nil)

Related

Present a transparent modal UIViewController

I'm trying to make a custom alertView (for iOS7+) on my own but I struggle with the alertView presentation.
I have a UIViewController with a black background (alpha set to 0.25f), and a alertView as subview.
When I want to show the alertView, I present modally the viewController:
-(void) show
{
UIWindow* window = [[UIApplication sharedApplication] keyWindow];
self.modalTransitionStyle = UIModalPresentationCustom;
self.transitioningDelegate = self;
[window.rootViewController presentViewController:self animated:YES completion:nil];
}
And here is my animator object:
-(NSTimeInterval) transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
NSLog(#"%s",__PRETTY_FUNCTION__);
return 2;
}
-(void) animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
NSLog(#"%s",__PRETTY_FUNCTION__);
UIView* toView = [transitionContext viewForKey:UITransitionContextToViewKey];
toView.alpha = 0;
UIView* container = [transitionContext containerView];
[container addSubview:toView];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.alpha = 0.5;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
The thing is: the modal VC is fading with the presenting VC in background as its supposed to do, but when the animation ends the presenting VC is removed from the background.
If I call [transitionContext completeTransition:YES]; instead, the presenting VC is in background but the modal VC is removed at animation end, so I guess the context cancels the presentation if we send 'NO'.
Is there a way to keep the presenting VC in background without having to make a snapshot of it and set it as background of the modal VC's view?
I've tried this solution and it works on both iOS 7 and 8:
if ([[UIDevice currentDevice].systemVersion integerValue] >= 8)
{
//For iOS 8
presentingViewController.providesPresentationContextTransitionStyle = YES;
presentingViewController.definesPresentationContext = YES;
presentedViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
}
else
{
//For iOS 7
presentingViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
}
Note: Be aware of the difference between 'presentingViewController' and 'presentedViewController'.
iOS8+
For iOS8+ you can use below code snippet
SecondViewController *secondViewController = [[SecondViewController alloc] init];
secondViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:secondViewController animated:YES completion:nil];
My case might differ from yours, but the information might be useful for the conversation.
In Storyboard, I changed my segue's Presentation to state "Over Full Screen" and it did the trick.
I think what you are seeing is the default behavior of iOS.
View controllers are not supposed to be non-opaque when presented as modal view controllers. iOS removes the underlaying view controller when the animation is complete, in order to speed up composition when the presented view controller is supposed to take up the entire screen. There is no reason to draw a view controller - which might be complex in it's view hierarchy - when it is not even visible on screen.
I think your only solution is to do a custom presentation.
Remark: I did not test this. But it goes something like this.
/* Create a window to hold the view controller. */
UIWindow *presenterWindow = [[UIWindow alloc] init];
/* Make the window transparent. */
presenterWindow.backgroundColor = [UIColor clearColor];
presenterWindow.opaque = NO;
/* Set the window rootViewController to the view controller you
want to display as a modal. */
presenterWindow.rootViewController = myModalViewController;
/* Setup animation */
CGRect windowEndFrame = [UIScreen mainScreen].bounds;
CGRect windowStartFrame = windowEndFrame;
/* Adjust the start frame to appear from the bottom of the screen. */
windowStartFrame.origin.y = windowEndFrame.size.height;
/* Set the window start frame. */
presenterWindow.frame = windowStartFrame;
/* Put the window on screen. */
[presenterWindow makeKeyAndVisible];
/* Perform the animation. */
[UIView animateWithDuration:0.5
delay:.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
presenterWindow.frame = windowEndFrame;
}
completion:^(BOOL finished){
/* Your transition end code */
}];
This does however leave you with no option to use any of the presenting view controller logic build into UIViewController. You'll need to figure yourself, when the presented view controller is done, and then reverse the animation and remove the UIWindow from screen.
The ViewController is not supposed to be transparent when you present it or push it. You can try adding it as subview. And for transition effect change its frame immediately after adding as subview. Make its frame somewhere outside the visible view and then animate it to change frame to visible view.
Hope this helps.
For your information,
I finally made my custom alertView a subclass of UIView for the "popUp part".
To show it, I just add the alertView as subview of the keyWindow with the constraints to center it, and put a transparent black background view behind it.
As it's not a controller, I have to manage UI rotation by myself (only for iOS 7, it rotates well with the UI in iOS 8).

On iOS 7, pushing a controller with a toolbar leaves a gap of unusable space if it's ultimately contained within a tab bar controller

In my iOS app, my window's rootViewController is a tab bar controller with the a hierarchy like this:
UITabBarController
UINavigationController 1
FirstContentController
UINavigationController 2
...
UINavigationController 3
...
...
When the user taps a certain row on FirstContentController, an instance of SecondController will be pushed onto its navigation controller. SecondContentController sets hidesBottomBarWhenPushed to YES in its init method and sets self.navigationController.toolbarHidden to NO in viewWillAppear:.
In iOS 6, the user would tap the row in FirstController and SecondController would get pushed onto the nav controller. Because it has hidesBottomBarWhenPushed set, it would hide the tab bar and, by the time the transition animation was complete, SecondController would be on the screen with its toolbar visible.
However, when testing this under iOS 7, hidesBottomBarWhenPushed's behavior seems to have changed. What I see now is:
the tab bar hides, as expected
the toolbar appears, as expected
a gap of unusable space exactly 49 pixels tall (the height of the tab bar) appears between the toolbar and the content view
The gap is completely unusable - it doesn't respond to touches and if i set clipsToBounds to YES on the main view, nothing draws there. After a lot of debugging and examining subview hierarchies, it looks like iOS's autosizing mechanism resizes the view controller's view to a height of 411 (on the iPhone 5). It should be 460 to reach all the way down to the toolbar, but the layout system seems to be including a "ghost" 49-pixel-tall tab bar.
This problem only occurs if the view controller has a tab bar controller as one if its parent containers.
On iOS 7, how can I have the tab bar disappear and a toolbar seamlessly slide into place when a new controller is pushed, and still have the view take up the entire space between the navigation item and the toolbar?
UPDATE
After further investigation, this only happens if SecondController's edgesForExtendedLayout is set to UIRectEdgeNone. However, unless I set that property to UIRectEdgeNone, the view's frame is too long and extends under the toolbar, where it can't be seen or interacted with.
I found that adding the following 2 lines of code in viewDidLoad of SecondViewController (where you want to hide TabBar but show the tool bar) fixes the problem.
self.extendedLayoutIncludesOpaqueBars = YES;
self.edgesForExtendedLayout = UIRectEdgeBottom;
My viewDidLoad of SecondViewController is as follows:
- (void)viewDidLoad {
[super viewDidLoad];
// These 2 lines made the difference
self.extendedLayoutIncludesOpaqueBars = YES;
self.edgesForExtendedLayout = UIRectEdgeBottom;
// The usual configuration
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
self.navigationController.navigationBar.translucent = NO;
self.navigationController.toolbarHidden = NO;
self.navigationController.toolbar.barStyle = UIBarStyleBlack;
self.navigationController.toolbar.translucent = NO;
.
.
}
But you need to fix the frame of the view manually as this causes the size to be (320x504). Which means it extends even behind the tool bar. If this is not a concern for you then this solution should work.
You will not like this answer This is not the answer you want, but after some research on hiding the tab bar in iOS7, my conclusion is: don't!
Tab bars have never been meant to be hidden - after all why have a UITabBarController if you want to hide the tab bar. The hidesBottomBarWhenPushed on view controllers is for hiding the bottom bar of a navigation controller, not tab bars. From the documentation:
A view controller added as a child of a navigation controller can display an optional toolbar at the bottom of the screen. The value of this property on the topmost view controller determines whether the toolbar is visible. If the value of this property is YES, the toolbar is hidden. If the value of this property is NO, the bar is visible.
Moreover, you are warned not to modify the tab bar object directly. Again, from the documentation:
You should never attempt to manipulate the UITabBar object itself stored in this property.
This is exactly what you are doing when setting it to hidden.
In iOS6 this has worked, but now in iOS7, it doesn't. And it seems very error prone to hide it. When you finally manage to hide it, if the app goes to the background and returns, Apple's layout logic overrides your changes.
My suggestion is to display your data modally. In iOS7 you can create custom transitions, so if it is important to you to have a push transition, you can recreate it yourself, although this is a bit over the top. Normal modal transition is something users are familiar, and actually fits this case better than push which hides the tab bar.
Another solution is to use a toolbar instead of a tab bar. If you use the navigation controller's toolbar for your tabs, you can then use hidesBottomBarWhenPushed as you require and it would give you the behavior you expect.
Uncheck "Hide bottoms bars on push" and set your autoconstraints as if there is a tab bar. Then in "ViewDidLoad" of the controller you want to hide the system tab bar, put the following code.
[self.tabBarController.tabBar setFrame:CGRectZero];
This makes sure the tab bar still accepts user interaction yet not visible to users. (other alternatives such as setting it 0 alpha or hidden will render tab bar useless) Now the autoconstaraints will make sure your view displays correctly with the tab bar height as zero.
It's a bug in iOS 7 UIKit due to this particular combination of:
UITabBarController
hidesBottomBarWhenPushed = YES
edgesForExtendedLayout = UIRectEdgeNone
UINavigationController toolbar
You should file a bug with Apple and include your sample code.
To work around the bug you need to remove one of those four conditions. Two likely options:
Fix the layout of your "second" view controller so that it works correctly when edgesForExtendedLayout is set to UIRectEdgeAll. This could be as simple as setting the contentInset on a scroll view.
Don't use UINavigationController's built-in toolbar. Instead, create a separate UIToolBar instance and manually add it to your second view controller's view.
You do have to set the tabBar of the TabBarController to hidden and your view should have autosizing set to flexible height.
With this code it's working:
#implementation SecondController
-(id)init
{
if( (self = [super init]) )
{
}
return self;
}
- (void)viewDidLoad;
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.tabBarController.tabBar.hidden = YES;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// will log a height of 411, instead of the desired 460
NSLog(#"frame: %#", NSStringFromCGRect(self.view.frame));
}
#end
Or, if you do want to use the hidesBottomBarWhenPushed method, you have to do this before you push the view controller obviously:
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
SecondController* controller = [[SecondController alloc] init];
controller.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:controller animated:YES];
}
If using the second method, your viewDidLoad method can get rid of flexible height method as well as tabBarHidden:
- (void)viewDidLoad;
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
self.edgesForExtendedLayout = UIRectEdgeNone;
}
See the result:
The key to this conundrum is that the navigationcontroller.view.frame size doesn't change. Going of batkin's Gist here is a gist of my own.
FirstViewController.m
#import "FirstController.h"
#import "SecondController.h"
#implementation FirstController
-(id)init
{
if( (self = [super init]) )
{
self.tabBarItem.title = #"Foo";
self.tabBarItem.image = [UIImage imageNamed:#"Tab Icon.png"];
}
return self;
}
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cell.textLabel.text = #"Click";
return cell;
}
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
SecondController* controller = [[SecondController alloc] init];
self.tabBarController.tabBar.hidden = YES;
[self.navigationController pushViewController:controller animated:YES];
}
#end
SecondViewController.m
#import "SecondController.h"
#implementation SecondController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.view.backgroundColor = [UIColor redColor];
self.view.clipsToBounds = YES;
/* ENTER VORTEX OF DESPAIR */
// without this, there's no gap, but the view continues under the tool
// bar; with it, I get the 49-pixel gap thats making my life miserable
self.edgesForExtendedLayout = UIRectEdgeNone;
//this resizes the navigation controller to fill the void left by the tab bar.
CGRect newFrame = self.navigationController.view.frame;
newFrame.size.height = newFrame.size.height + 49;
self.navigationController.view.frame = newFrame;
/* EXIT VORTEX OF DESPAIR */
self.navigationController.toolbarItems = #[
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:nil action:nil]
];
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationController.toolbarHidden = NO;
// will log a height of 411, instead of the desired 460
NSLog(#"frame: %#", NSStringFromCGRect(self.view.frame));
NSLog(#"frame: %#", NSStringFromCGRect(self.navigationController.view.frame));
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.tabBarController.tabBar.hidden = NO;
self.navigationController.toolbarHidden = YES;
//this resizes the navigation controller back to normal.
CGRect newFrame = self.navigationController.view.frame;
newFrame.size.height = newFrame.size.height - 49;
self.navigationController.view.frame = newFrame;
//this is optional and resizes the view to fill the void left by the missing toolbar.
CGRect newViewFrame = self.view.frame;
newViewFrame.size.height = newViewFrame.size.height + 49;
self.view.frame = newViewFrame;
}
#end
If you are using Auto Layout,make sure you pin the view to its superview instead of Top Layout Guide or Bottom Layout Guide.
Have you tried to move your call hidesBottomBarWhenPushed in the viewDidLoad or before the secondViewController is pushed?
With ios7, a lot of timing issues appear if you don't do the calls at teh good moment.
You mention that you can fix this by not touching the edgesForExtendedLayout. Is there a necessary reason that the content/controls of the view controller are contained in the root view of the pushed view controller? You might consider wrapping everything in a view that is the first and only child of the main view. Then adjust that view's frame in the viewDidLayoutSubviews of the pushed view controller to avoid having content permanently beneath the toolbar using the top/bottomLayoutGuide of the view controller.
I built a new project using your Gist, and I encased the UITabBarController in a UINavigationController:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
UITabBarController* tabController = [[UITabBarController alloc] init];
tabController.viewControllers = #[
[[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]],
[[UINavigationController alloc] initWithRootViewController:[[FirstViewController alloc] init]]
];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tabController];
[navController setNavigationBarHidden:YES];
self.window.rootViewController = navController;
return YES;
}
And to show the SecondViewController, here is what I did:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SecondViewController* controller = [[SecondViewController alloc] init];
// Reaching the UITabBarViewController's parent navigationController
[self.parentViewController.navigationController pushViewController:controller animated:YES];
}
Finally, in the secondViewController:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.view.backgroundColor = [UIColor redColor];
self.view.clipsToBounds = YES;
// The following line only works in iOS7
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
[self.navigationItem setRightBarButtonItem:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:nil action:nil]];
UIBarButtonItem * logoutButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:nil action:nil];
NSMutableArray * arr = [NSMutableArray arrayWithObjects:logoutButton, nil];
[self setToolbarItems:arr animated:YES];
[self.navigationController setNavigationBarHidden:NO animated:YES];
[self.navigationController setToolbarHidden:NO animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:YES];
[self.navigationController setToolbarHidden:YES animated:YES];
}
Here's what it does look:
EDIT: Changed the example and changed the screenshot. Made the example iOS6 compatible.
I manually manage hide/unhide of bottom-tab-bar along with fade animation by
...
[self.tabBarController.tabBar setHidden:NO];
[self.tabBarController.tabBar setAlpha:0.1];
[UIView animateWithDuration:0.2 animations:^{
[self.tabBarController.tabBar setAlpha:1.0];
}];
...
Bottom Toolbar on SecondVC was added in IB. No problem so far. Using Storyboard.
I think you can set SecondController's edgesForExtendedLayout to UIRectEdgeBottom.
This helps me:
Choose you view controller in storyboard -> Go to properties -> Uncheck "Adjust Scroll View Insets"
As #Leo Natan is pointing out, it seems as if hiding the tab bar and showing a toolbar is discouraged.
Nevertheless, there is a very easy solution that is working:
Just check "Under Opaque Bars" in the view controller properties in the storyboard as shown below:

presentViewController:animated:completion: does not animate View Controller into view

I am trying to debug a really weird issue in the following code:
if(condition1) {
ImageViewController* imageViewer = [[ImageViewController alloc] initWithImageData:tappedItem];
[self presentViewController:imageViewer animated:YES completion:^{
[imageViewer loadImage];
}];
}
else if(condition2) {
DocumentViewController* docViewer = [[DocumentViewController alloc] init];
[self presentViewController:docViewer animated:YES completion:nil];
}
In other words, depending on the state of condition1 and condition2, one of two subclasses of UIViewController will be displayed modally to the user.
In the second case all is well, but in the first the view controller is not presented with the usual animation that shows it sliding in from the bottom of the screen. Instead, after a brief delay, it just shows up all of a sudden, covering the entire screen. Another oddity is that in the dismissal animation, the image view inside the view controller is transparent.
Removing the completion block has no effect. Replacing my view controller with an instance of UIViewController also has no effect, other than demonstrating that for some reason, animations don't work for UIViewController instances either.
Thinking that maybe I did something wrong in viewDidLoad etc, I tried commenting out the view load/appear methods but to no avail.
Pushing the view controller onto the nav stack is not an option because the app has a tab bar and I don't want to be visible.
update
Replacing my instance of ImageViewController with a DocumentViewController does result in an animation. The question now becomes: what could I have done in ImageViewController to mess up the animation?
I've found a solution but I still have no idea what the real cause was.
The fix was to set a background color for the view of the UIViewController being displayed modally in its viewDidload method e.g.
self.view.backgroundColor = [UIColor grayColor];
If I ever figure out what really happened, I will post here.
How about presenting the view controller on the tab bar controller:
if(condition1) {
ImageViewController* imageViewer = [[ImageViewController alloc] initWithImageData:tappedItem];
[self.tabBarController presentViewController:imageViewer animated:YES completion:^{
[imageViewer loadImage];
}];
}
else if(condition2) {
DocumentViewController* docViewer = [[DocumentViewController alloc] init];
[self.tabBarController presentViewController:docViewer animated:YES completion:nil];
}
It happened to me as well.. changing the background colour didn't really help.
I did the following - it turns up to be quite nice:
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:NO];
self.view.userInteractionEnabled = FALSE;
self.view.hidden = TRUE;
self.navigationController.navigationBar.alpha = 0;
}
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:NO];
float width = self.view.frame.size.width;
float height = self.view.frame.size.height;
self.view.frame = CGRectMake(0, height, width, height);
self.view.hidden = FALSE;
[UIView animateWithDuration:0.7f animations:^{
self.view.frame = CGRectMake(0, 0, width, height);
self.navigationController.navigationBar.alpha = 1;
} completion:^(BOOL finished){
self.view.userInteractionEnabled = TRUE;
}];
}
Setting the background color works for me in iOS 8.
Also uncheck the opaque setting in the Interface Builder works!

How to show UIViewController in other UIViewController?

This is typical question and possibly duplicated, but..
There is iPad app which has UINavigationBar and UITabBar. I have created a button on navigationBar which must show appinfoViewController.
When I present it navigationBar and tabTab are still available to tap. But I would like to show appinfoViewController to full app's main screen size (like modal)
This is my code:
- (void)showInfo
{
AboutViewController *about = [[AboutViewController alloc] init];
[about.view setFrame: self.view.frame];
[about.view setAlpha:0.0];
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
[about.view setAlpha:1.0];
[self.view addSubview:about.view];
[self addChildViewController:about];
[about didMoveToParentViewController:self];
}
completion:^(BOOL finished){
}];
}
How to present appinfoViewController to full screen?
May be it's possible to present it modally but without BLACK background?
As suggested in the comments, using
[self presentViewController:about animated:YES completion:nil]
would get rid of the nav bar and the tab bar. If you need to keep them, you need to use
[self.navigationController pushViewController:about animated:YES];
EDIT:
In order to have the user interaction disabled everywhere except for your about view, it's slightly trickier: first off, you need to have all of your UI elements embedded in a view that is not the main view of your presenting view controller.
Let's say you have only a button (the "show about" button), you wouldn't just place it in your main view, but you would use another view (let's call it "outer view") that is just as big as the view controller's view and where you place the button (along with any other ui element you might have). You also need an outlet to this outer view. Then write a method such as:
-(void)userInteractionEnabled:(BOOL)enabled
{
self.navigationController.navigationBar.userInteractionEnabled = enabled;
self.tabBarController.tabBar.userInteractionEnabled = enabled;
self.outerView.userInteractionEnabled = enabled;
}
Alternatively you could simply disable every "interactive" outlet instead of outerView. So if, for example, you have 2 textviews, 3 buttons and one UIPickerView, you would set userInteractionEnabled = enabled for each of those outlets (instead of doing it only for the parent view).
Now, in your showInfo method you can have:
CGRect frame = CGRectMake(50, 50, 200, 200); //Use whatever origin and size you need
about.view.frame = frame;
[self.view addSubview:about.view];
[self userInteractionEnabled:NO]
And in your btnClose method you can just put:
[about.view removeFromSuperview];
[self userInteractionEnabled:YES];
I hope this helps, let me know if this is what you needed!
P.S. Maybe you're already aware of this, but there is a class UIPopoverController, only available for iPad's apps, that would pretty much do all of this for you. You can find a tutorial on how to use it here.
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
AboutViewController *about = (AboutViewController *)[storyBoard instantiateViewControllerWithIdentifier:#"about"];
[self.navigationController pushViewController:about animated:YES];
You can add viewcontroller view layer directly to presenting viewcontroller view.
Code should be look like --
AboutViewController *about = [[AboutViewController alloc] init];
[about.view setFrame: self.view.bound];
[about.view setAlpha:0.0];
[self.view addSubview:about.view];
[UIView animateWithDuration:0.5
delay:0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
[about.view setAlpha:1.0];
}
completion:^(BOOL finished){
}];

How to transition from vc1.view to vc2.view in iOS, without any navigation controller?

I have vc1.view covering the whole screen, and I want to be able to dim vc1.view, and have vc2.view zoom into the whole screen.
I don't have any navigation controller in the app, so what's the best practice to achieve my goal? The solution I'm thinking of is:
Add both vc1.view and vc2.view into a common container view
Use [UIView transitionFromView:vc1.view toView:vc2.view ......]
I dislike the idea of having to add views of different vc into a common container view. Any suggestions? Thanks in advance.
You can use transitionFromView:toView:... without adding the new view to a common container, because that transition method takes care of adding the view. The following worked for me. The code is in the view controller whose view is the "from view". I'm using a cross fade here, but you could change that to any of the other available methods:
-(void)switchViews:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = self.view.frame;
[UIView transitionFromView:self.view toView:yellow.view duration:2 options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
win.rootViewController = yellow;
}];
}
However, to do a custom transition, you do have to add the new view as a subview of whatever view the "from view" is in (I think). In this example, that is the window's view. This code grows the new view from the center of the old one, while that one fades out. At the end of the transition, the view controller is switched to the one that owns the new view (yellow in this case)
After Edit: I changed this method to use a CGAffineTransform (thanks to jrturton for that suggestion made in an answer to my question):
-(void)switchViews3:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = self.view.frame;
yellow.view.transform = CGAffineTransformMakeScale(.1, .1);
[win addSubview:yellow.view];
[UIView animateWithDuration:.6 animations:^{
yellow.view.transform = CGAffineTransformIdentity;
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}
To present from vc1 to vc2 without a navigation controller, use
[vc1 presentViewController:vc2 animated:YES completion:nil];
To change the presenting style, Apple provides a few. You just need to set it before calling the above code:
vc2.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
Here is the list:
typedef enum {
UIModalTransitionStyleCoverVertical = 0,
UIModalTransitionStyleFlipHorizontal,
UIModalTransitionStyleCrossDissolve,
UIModalTransitionStylePartialCurl,
} UIModalTransitionStyle;

Resources