How can one present a UINavigationController such that the view it was presented from is still visible in the background?
My experience is that the UINavigationController will clip anything below its view, so setting UINavigationController.view.alpha will uncover a fixed color background, not the presenting view's content.
Can this be changed?
EDIT
I am not interested in the navigation bar, but the full content the navigation controller manages.
The problem is not the UINavigationController, but the fact that you present it modally. ViewControllers presented modally can never be transparent.
You can fake a modal presentation by adding you UINavigationControllers view as a subview to the main UIWindow.
This example works for me when testing in XCode:
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.35];
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:viewController];
[[[[UIApplication sharedApplication] delegate] window] addSubview:navCon.view];
Of course you will have to do any animated transition yourself, but that should be trivial using animation blocks.
There is now a way to achieve this using the iOS7 custom transitions, this way :
MyController * controller = [MyController new];
[controller setTransitioningDelegate:self.transitionController];
controller.modalPresentationStyle = UIModalPresentationCustom;
[self controller animated:YES completion:nil];
To create your custom transition, you need 2 things :
A TransitionDelegate object (implementing
<UIViewControllerTransitionDelegate>)
An "AnimatedTransitioning" object
(implementing <UIViewControllerAnimatedTransitioning>)
You can find more informations on custom transitions in this tutorial : http://www.doubleencore.com/2013/09/ios-7-custom-transitions/
if you wish this behavior you can not use a UINavigationController, as you say the navigation controller clip the view of the content controller. To do what you want you should add a navigator bar to your view instead, and simulate the actions of the navigation controller. To create a back button similar to the controller read this article
Related
I implemented a custom tab bar controller as a set of buttons each one related to it's own View Controller. I guided on this link http://idevrecipes.com/2011/01/04/how-does-the-twitter-iphone-app-implement-a-custom-tab-bar/ to achieve the behavior. So the relevant part of code is as follows:
- (void) selectedItemAtIndex:(NSUInteger)itemIndex
{
// Get the right view controller
NSDictionary* data = [self.tabBarItems objectAtIndex:itemIndex];
UIViewController* viewController = [data objectForKey:#"viewController"];
// Remove the current view controller's view
UIView* currentView = [self.view viewWithTag:SELECTED_VIEW_CONTROLLER_TAG];
[currentView removeFromSuperview];
// Set the view controller's frame to account for the tab bar (+ 48)
viewController.view.frame = CGRectMake(0,48,self.view.bounds.size.width, self.view.bounds.size.height - 48);
// Se the tag so we can find it later
viewController.view.tag = SELECTED_VIEW_CONTROLLER_TAG;
// Add the new view controller's view
[self.view insertSubview:viewController.view belowSubview:self.tabBar];
//Keep track of current view controller
self.currentController = viewController;
}
So far is working, I can see each view controller in a similar maner to the default TabBarViewController. But then there's a requirement where I need to push a new navigation controller modally (it should take all the application frame) from inside one of the tabBar controllers.
At first glance I tried the following code from within one of the tab controllers:
DetailViewController *detailViewController = [[DetailViewController alloc]init];
UINavigationController *navigationController = [[UINavigationController alloc]detailViewController];
[self presentModalViewController:navigationController animated:YES];
However is not working as expected, first the view is shown below the TabBar and second the new view is not taking in consideration the parent view frame which should be the screen bounds less the tabbar. (0, 48, 360, 412). My detail view controller it's loading content from a nib file.
Well, this is quite obvious since the TabBar Controller is inserting each view below my custom TabBar.
[self presentModalViewController:navigationController animated:YES];
So I tried inserting it directly as a window subview:
[[UIApplication sharedApplication].keyWindow addSubview:navigationController.view];
But, I think this is not okay... there should be a better approach that I can't figure out. So if anybody could give me suggestions on how to correct or improve this navigation system it would be great.
Thanks a lot.
If you are building you app for iOS 5.0 and up you can make use of childViewController. In your custom Tab Bar you can have a containerView and a tabView.
The view of viewController is added to containerView. All the necessary events are generated to the subsequently added viewController if the following methods are implemented correctly
- (void)addChildViewController:(UIViewController *)childController;
- (void)removeFromParentViewController;
More about viewController containment can be found here.
I am trying to modal present a view controller like below:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"addPopover"];
vc.view.backgroundColor = [UIColor clearColor];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:vc animated:YES];
Now using UIModalPresentationCurrentContext means I can present this view with a transparent background and see my other view behind the new one. However, it stops me from being able to present it with a transition.
Any ideas why? Or how I can get around this? Thanks.
I was able to accomplish this by setting modalPresentationStyle = UIModalPresentationCurrentContext on the rootViewController of my UIWindow, IF I haven't presented any new full screen viewControllers on top of this rootViewController. I did something like this:
UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:vc animated:YES completion:nil];
I've tried this with both a UINavigationController as my window's rootViewController and various other custom view controllers. It seems as long as the window's rootViewController knows what's going on, any sub-viewControllers can call presentViewController:animated:completion: without blacking-out the underlying view.
However, let's say you present another viewController on top of your window's rootViewController. And this new viewController is presented with modalPresentationStyle = UIModalPresentationFullScreen (ie. takes up the screen), then you have to call modalPresentationStyle = UIModalPresentationCurrentContext on that top-most viewController.
So to recap:
If you have UINavigationController -> UIViewController(s) (they could be pushed and popped in and out), then you set modalPresentationStyle = UIModalPresentationCurrentContext on the UINavigationController.
If you have UINavigationController -> UIViewController -> new-UIViewController (with modalPresentationStyle set to UIModalPresentationFullScreen), then you set modalPresentationStyle = UIModalPresentationCurrentContext on the new-UIViewController.
Hopefully this works for you guys as well!
Full screen modals aren't supposed to allow you to see the under layer. Annoying I know.
From your code I'm assuming that "addPopover" is actually a full screen view, where you have your content as a subsection while the rest is transparent.
Try this:
vc.modalPresentationStyle = UIModalPresentationCurrentContext;
vc.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:vc animated:YES completion:NULL];
Note: I'd recommend adding a low alpha background color to let the user know that they can't interact with the rest of the view when you pop this over top...
Mr. T's suggestions worked nearly perfectly, you just lose the transition animation on the presented viewcontroller
Since it is also setting the appDelegates rootviewController's presentation style, it will also remove the transition animations for any views presented from that point.
My fix was to return the AppDelegates rootViewController's presentation style back to it's default whenever the viewcontroller is closed that I needed the transparent background on
I have a button that dismisses the presentedViewController, in that I also set the presentation style of the rootViewController back to default using:
UIViewController *rootViewController = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
rootViewController.modalPresentationStyle = UIModalPresentationFullScreen;
I hope that helps anyone else getting stumped on this problem.
This is what I am doing. I have a tabBarControllerOne with 5 tabs. On clicking one of the tabs, I present a modal view controller, which has a navigationBar and a TabBarControllerTwo (with 3 tabs). These three tabs are the matter for concern here.
In the 5th Tab of tabBarController I show modalViewController as
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.nextTabView];
// navController.navigationBarHidden = YES;
navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
NSLog(#"Displauing the navcontroller before pushing %#", navController);
[self presentModalViewController:navController animated:NO];
Here, nextTabView is a tabBarController with 3 tabs. The views work. In the views, if I try something like.
self.navigationController.navigationBarHidden = YES;
[self.navigationController pushViewController: someController animated:YES];
// nothing works.
If I NSLog, it displays self.navigationController as (null)
Can someone tell me why this is not working ?
Embedding a UITabBarController inside a UINavigationController is not supported. Apple has a careful hierarchy of container view controllers, and a UITabBarController must be the root of its view controller hierarchy.
Additionally, as Joe points out, your views don't belong to the navigation controller; they belong to the tab bar controller, so their navigationController property is not set.
The modal view controller does not belong to a UINavigationController stack therefore the property is not set. You will want to use delegation to notify the creating controller when something is selected then that controller can properly push the next controller on to the stack.
UIViewController Reference:
Discussion
Only returns a navigation controller if the view controller
is in its stack. This property is nil if a navigation controller
cannot be found.
I am showing a modal view which is a UITableViewController class. For some reason it won't show the navigation bar when I show it. Here is my code:
SettingsCreateAccount *detailViewController = [[SettingsCreateAccount alloc] initWithStyle:UITableViewStyleGrouped];
detailViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
detailViewController.navigationController.navigationBarHidden = NO;
[self.navigationController presentModalViewController:detailViewController animated:YES];
detailViewController = nil;
[detailViewController release];
I thought it was shown by default? If it helps, I am calling this from another class that is also a UITableViewController managed by a UINavigationController. Ideas?
When you present a modal view controller it does not use any existing navigation controllers or navigation bars. If all you want is to display a navigation bar, you need to add the navigation bar as a subview of your modal view and present it as you're doing.
If you want to present a modal view controller with navigation functionality, you need to present a modal navigation controller containing your detail view controller instead, like so:
SettingsCreateAccount *detailViewController = [[SettingsCreateAccount alloc] initWithStyle:UITableViewStyleGrouped];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
[detailViewController release];
navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:navController animated:YES];
[navController release];
Your modal controller will manage its own navigation stack.
Here is one way to display navigation bar for those who are using storyboards, suggested by Apple's Tutorial on Storyboard.
Because a modal view controller doesn’t get added to the navigation stack, it doesn’t get a navigation bar from the table view controller’s navigation controller. To give the view controller a navigation bar when presented modally, embed it in its own navigation controller.
In the outline view, select View Controller.
With the view controller selected, choose Editor > Embed In > Navigation Controller.
On iOS 7 and you just want a navigation bar on your modal view controller to show a title and some buttons? Try this magic in your UITableViewController:
// in the .h
#property (strong) UINavigationBar* navigationBar;
//in the .m
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = #"Awesome";
self.navigationBar = [[UINavigationBar alloc] initWithFrame:CGRectZero];
[self.view addSubview:_navigationBar];
[self.navigationBar pushNavigationItem:self.navigationItem animated:NO];
}
-(void)layoutNavigationBar{
self.navigationBar.frame = CGRectMake(0, self.tableView.contentOffset.y, self.tableView.frame.size.width, self.topLayoutGuide.length + 44);
self.tableView.contentInset = UIEdgeInsetsMake(self.navigationBar.frame.size.height, 0, 0, 0);
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
//no need to call super
[self layoutNavigationBar];
}
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[self layoutNavigationBar];
}
I want to share how the accepted solution can be used in projects with storyboards:
The simple approach is to put in a storyboard blank navigation controller before the VC which is to be presented modally, so the relations look like:
(Presenter VC) -> presents modally -> (navigation controller having a controller to be presented as its root).
We've tried this approach for a while and noticed that our storyboards become "polluted" by a large number of such intermediate navigation controllers when each! of them is used exclusively for one! presentation of some other controller, that we want to be presented modally with navigation bar.
Our current solution is to encapsulate the code from accepted answer to a custom segue:
#import "ModalPresentationWithNavigationBarSegue.h"
#implementation ModalPresentationWithNavigationBarSegue
- (void)perform {
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:self.destinationViewController];
[self.sourceViewController presentViewController:navigationController animated:YES completion:nil];
}
#end
Having this segue in our project we do not create intermediate navigation controllers in our storyboards anymore, we just use this ModalPresentationWithNavigationBarSegue like:
Presenter VC --> Presentee VC
I hope that this answer will be helpful to people who like to avoid unnecessary duplication in their apps storyboards.
I just wanted to add something to what #Scott said. His answer is definitely the easiest and most accepted way of doing it now with Storyboards, iOS 7 and 8... (and soon, 9).
Definitely adding a view controller to the Storyboard and Embedding it as described by #Scott is the right way to go.
Then, just add the segue by control-dragging from the source view controller to the target (the one you want to show modally), select "Present Modally" when the little view appears with the choices for the type of segue. Probably good to give it a name too (in the example below I use "presentMyModalViewController").
One thing that I needed that was missing is #Scott's case is when you want to actually pass on some data to that modally-presented view controller that is embedded in the navigation controller.
If you grab the segue.destinationViewController, it will be a UINavigationController, not the controller you embedded in the UINavigationController.
So, to get at the embedded view controller inside the navigation controller, here's what I did:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"presentMyModalViewController"]) {
// This could be collapsed, but it's a little easier to see
// what's going on written out this way.
// First get the destination view controller, which will be a UINavigationController
UINavigationController *nvc = (UINavigationController *)segue.destinationViewController;
// To get the view controller we're interested in, grab the navigation controller's "topViewController" property
MyModalViewController *vc = (EmailReceiptViewController *)[nvc topViewController];
// Now that we have the reference to our view controller, we can set its properties here:
vc.myAwesomeProperty = #"awesome!";
}
}
Hope this helps!
If you only need a NavigationBar, you can add an instance of UINavigationBar and assign BarItems to it.
Hi I'm having this problem:
I have a UITabBarController with UINavigationControllers in each tab.
However, I'm trying to implement an action that, when I click on a button, should present me a new view with a UINavigationController (since it will be multi-view) in a modal way.
What I've tried is to implement a new UIViewController, with a NavigationBar. And then on the button handler I wrote something like this:
SendMessageViewController *v = [[SendMessageViewController alloc] initWithNibName:#"SendMessageView" bundle:nil];
UINavigationController *t = [[UINavigationController alloc] initWithRootViewController:v];
[(UINavigationController *)[tabbar.viewControllers objectAtIndex:0] presentModalViewController:t animated:true]; // hardcoding 0, I know
However, the modal view that's pushed appears with 2 navigation bars, one empty, and my other defined on the "SendMessageView" XIB.
Is there a way to solve this? I don't want to create a XIB just to contain a UINavigationController.. I thought I could do it programatically.
What you want to do is this:
[tabBarController presentModalViewController:t animated:Y];
Basically, get a handle to the tab bar ViewController, and then you'll have the single nav bar you were seeking.