Is it possible to use a UINavigationController in such a way that it doesn't use the full window?
I've tried setting it's view's frame as well as adding it's view to another (non-fullscreen) view instead of the window, and neither seems to work.
You cannot directly change the size of a UINavigationController or it's subviews directly, as the UINavigationController automatically resizes them to full screen, no matter what their frames are set to. The only way I've been able to overcome this so far is as follows:
First, create an instance of UINavigationController as you normally would:
UINavigationController *nc = [[UINavigationController alloc] init];
self.navController = nc;
[nc release];
Then, create an instance of UIView, constrained to the size you really want:
UIView *navView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, DESIRED_HEIGHT)];
navView.clipsToBounds = YES;
[navView addSubview:self.navController.view];
[self.view addSubview:navView];
[navView release];
The navView's clipsToBounds property must be set to YES, or the UINavigationController and it's view will still appear fullscreen. Then, add the UINavigationController to that constrained view. This UIView may then be added to the UIViewController's view, as seen above.
The thing to note is that any UIViewController's views that are added to the UINavigationController will all have their content constrained to navView's bounds, not the frame of the subviews added to the UINavigationController, so the content in each subview should be created to properly display for the navView's bounds.
In any case, this technique does work, as I've created an app that successfully uses it. The only other way I've ever gotten this to work is to create a custom navigation controller class from scratch, replicating the functionality of UINavigationController, but without the automatic resizing (which I've also done in the past), and that can be a pain. Hope this helps.
This is my first post ever, although I have been learning an enormous amount from this community. So I wanted to thank you all for this.
My challenge, and the reason I'm posting here, was to take the answer to this question and refactor it for my needs, using iOS5 and storyboards. This solution probably won't work well for older implementations, but I thought I'd post it anyway.
Here's what I ended up with, and it works well (iPad app). This is all set up on my default UIViewController, set as root in storyboard view.
Hope this helps out!
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
/*vars:
rightSideView is the containing view - this is where the UINavigationController will sit, along with it's view stack
myStoryboard is self-explanatory I think
myViewController is identified as in storyboard as "accountView", to be pulled from the storyboard and used as the rootview
*/
//Steps
//Add subview to this controller's view (for positioning)
UIView *rightSideView = [[UIView alloc]initWithFrame:CGRectMake(30, 30, 500, 600)];
rightSideView.clipsToBounds = YES;//this little baby makes sure that the damn navigation bar clips!!
rightSideView.backgroundColor = [UIColor grayColor];//so I can see it
//instantiate view controller for nav controller's root view
UIStoryboard *myStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:[NSBundle mainBundle]];
UIViewController *myViewController = [myStoryboard instantiateViewControllerWithIdentifier:#"accountView"];
//create NavController
UINavigationController *myNavController = [[UINavigationController alloc]initWithRootViewController:myViewController];
//Add navController as one of my child ViewControllers
[self addChildViewController:myNavController];
//Add NavController's view into my constrained view
[rightSideView addSubview:myNavController.view];
//Finally, add right side view as a subview of myself
[self.view addSubview:rightSideView];
}
This might help?
Adding a UINavigationController as a subview of UIView
You could always use the UINavigationBar class directly, and implement the controlling code yourself.
This is hard to answer because it's complicated.
On iPhone, you cannot have a UINavigationController that is shorter than the screen.
So if you'd like to show an ad banner, show it above the bottom toolbar or below the top navigation bar.
On iPad you can have two UINavigationControllers side by side, but in my case, they still take the entire height of the screen. Given the behavior of iPhone I didn't try to modify the height behavior on the iPad.
Related
I am in the process of making my first iPhone app with storyboard in Xcode and am stuck. My main screen has a view controller called MemberViewController, which is the home screen. This screen has a UIView that is smaller than the view controller called mainContent.
So basically, I want to be able to load different view controllers (all the same size as the UIView) inside the UIView.
MemberViewController (home page)
-mainContent (UIView)
GetStartedViewController (separate view controller that I want to show inside the UIView)
ProfileViewController (separate view controller that I want to show inside the UIView)
For example, I want the GetStartedViewController to have a button that I can press to switch to the ProfileViewController. The view controllers need to be able to replace each other inside the mainContent UIView.
Thank you so much in advance for your help. If there's an easier way to do this, please let me know.
I use this technique a lot actually. My typical setup of a root UIViewController (you call it memberController) has a view with a UITabBar at the bottom, and then another UIView (you call it mainContent) which contains the rest of the space above that bar.
memberController stays on the screen all the time. Inside of mainContent, add a UINavigationController and initialize it with your first content-carrying GetStartedViewController. When you want to switch tabs on your tab bar, send the appropriate message to this UINavigationController and the views will change inside.
HINT: say your UINavigationController is called navController - you can get rid of the navigation bar (blue one at the top) by sending the message [navController setNavigationBarHidden:TRUE];
EDIT: The code you requested looks like this. This adds a nav controller to a window's view in the applicationDidFinishLaunchingWithOptions method. Instead of window, just do this same thing on your view controller in the viewDidLoad.
window and navController are both properties
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
self.navController = [[UINavigationController alloc] initWithRootViewController:[YourViewController new]];
[self.navController setNavigationBarHidden:YES];
[self.window addSubview:self.navController.view];
[self.window makeKeyAndVisible];
Hope this helps!
So I'm building an app and I am running through a few ViewControllers that don't need to know about each other, so I start off switching through views like so...
// remove the previous view in order to load in the new view
NSArray *sViews = [self.view subviews];
[sViews makeObjectsPerformSelector:#selector(removeFromSuperview)];
// create the new view, in this case the user wishes to
BaseViewController *baseVC = [[BaseViewController alloc] initWithNibName:#"BaseViewController" bundle:[NSBundle mainBundle]];
self.baseViewController = baseVC;
[baseVC release];
// add the newly created view to the screen
[self.view insertSubview:baseViewController.view atIndex:0];
The above is the view controller that I want the navigation controller to reside in. So within the .m of this view controller I created a UINavigationController as a member variable and named it navController. I then tried implementing a UINavigationController using the code below.
UIViewController *control = [[BusinessDisplayViewController alloc] initWithNibName:#"BusinessDisplayViewController" bundle: nil];
navController = [[UINavigationController alloc] initWithRootViewController:control];
[self presentModalViewController:navController animated:YES];
The problem I'm running into is two fold. First, when the BusinessDisplayViewController (below) is loaded there is a 20 pixel or so gap between my mapView and tableView that isn't there when I was loading it using insertSubview: not sure why that would be. Second, once I'm in BusinessDisplayViewController.m I'm not sure how to access the navigationController created in BaseViewController. Could someone explain why my view would be effected, how I could access the navigationController or if I'm even going about this the right way.
UINavigationController is designed for use in one of three possible contexts on iPhone:
As the app's root view controller, with its view added as a subview of the app's window.
As one of the viewControllers of a UITabBarController.
Presented as a full screen view controller via presentModalViewController:animated:.
In your case, the UINavigationController has configured itself for presentation as a subview of the window. This is why you see the 20 pixel gap at the top. Since the window object underlaps the status bar, UINavigationController offsets the position of its navigation bar by 20 pixels (or more, if you're on a phone call).
The standard way to use UINavigationController as your root view controller is to construct it as a property of your app delegate in application:didFinishLaunchingWithOptions:, and add its view as a subview of the window. Then within any view controller you push onto your navigation stack, you can access the navigation controller object using self.navigationController.
Usually, you want your UINavigationController to be at the root level, Is there a specific reason for having your app setup this way? To answer your question though, you can access the variable by setting a property for it, then using the dot notation: baseVC.navController.
For the 20 pixel space problem, post your BaseViewController view related code. It is probably a bounds vs frame issue.
New to iPhone development, but I've been given a big project as a first go and I'm a bit stuck.
Basically the app will start with a settings screen, then you click a button to go to a dashboard with multiple option buttons. Each button will lead to a different Navigation View with tables.
The way I've approached this is to start with a UIViewController with a button, which I've got wired up but when you hit the button and I do:
[self.view removeFromSuperview];
UIViewController *newView = [[UIViewController alloc] initWithNibName:#"Dashboard" bundle:nil];
[self.view addSubview:newView.view];
the second view isn't loading. I just get a blank screen. Do I need to make a reference in the first controller to the second?
Also, am I approaching this in the right way? As long as I removeFromSuperview will I be able to load the navigation controllers on the press of a button?
Sorry if this isn't too clear, I've been through books and lots of websites but don't seem to be able to get my head around this.
Thanks
There is nothing here with the new view, rather the problem is with current view. You have removed the self.view from super view.
[self.view removeFromSuperview];
So anything added to self.view will not be shown, as self.view itself is removed.
When presenting child controller/view from a parent controller, you should consider using presentViewController. Eventually, use dismissViewControllerAnimated when you want child to disappear and parent to reappear.
In parent view controller:
ChildViewController * child = [[ChildViewController alloc] init];
[self presentViewController:child animation:YES completion:Nil];
In child view controller, ie. in some action handler:
-(IBAction)close:(id)sender
{
[self dismissViewControllerAnimated:YES completion:Nil];
}
IMHO you should also get in the habit of naming instance variables to what they are instantiated from. In your example you name the instance newView, when it should be something like newViewController. That way you make sure you don't mix up views with view controllers.
[self.view removeFromSuperview];
You've removed the view from the superview
[self.view addSubview:newView.view];
But you're adding the new view to the same view that you have just removed from the superview. It's not displaying anywhere.
Your third line adds newView as a subview of self.view, but you just removed self.view from it's superview.
I'd suggest reading more about view controllers. You'll want to have one view controller per "screen", so one for your settings screen, one for your dashboard, one for each table, and so on. Then, manage which one is visible by pushing and popping these view controllers from the nav controller's stack.
This removes self.view, which will most likely destroy the object since there will be no other references to it:
[self.view removeFromSuperview];
Here you are creating an UIViewController, and adding it's view to self.view, which is probably not what you want:
UIViewController *newView = [[UIViewController alloc] initWithNibName:#"Dashboard" bundle:nil];
[self.view addSubview:newView.view];
Look into UINavigationController so that you can easily swap screens in and out with some built in animations. Here's a bit more about them. Here's a tutorial.
The UIViewController's view should not be removed from or added to a view hierarchy outside the control of the view controller. While you might be able to get that manipulation to work now it won't in the future.
Read up on view controllers here.
The basic idea is that you present the view controller then it will take care of manipulating the view hierarchy for you.
So a better approach to get started would be to do something like this;
[viewController1 presentModalViewController:viewController2 animated:YES];
This line of code will present viewController2 with the default modal animation (slide in from the bottom). If you'd like a different animation you can change the modalPresentationStyle to one of the constants in the UIModalPresentationStyle enum on viewController1 (note thats a viewController1, not viewController2).
If you want something more like the Clock app look into the tab bar controller. If you want something more like the Mail app look into the navigation controller.
Ok I have a view that we will call homeView. When a certain button is pushed on homeView, a UIPopOverController is presented in the following way:
self.studyPicker = [[[StudyPickerController alloc] initWithStudyArray:self.studyArray ViewNum:butto.tag] autorelease];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.studyPicker];
_studyPicker.delegate = self;
self.studyPickerPopover = [[[UIPopoverController alloc] initWithContentViewController:navController] autorelease];
[self.studyPickerPopover presentPopoverFromRect:CGRectMake(955,60, 22,10) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
Which works great. I get a nice big popover controller that displays my tableView data just the way I need it. Just as an FYI, in my StudyPickerController viewDidLoad method, I am also setting this:
self.contentSizeForViewInPopover = CGSizeMake(1000.0, 610.0);
Which allows me to make the size of my tableView nice and big. The problem I am experiencing is after I select a row in this first tableView. The navigationController pushes the new view just fine, but then the width of my popover controller gets cut in half! It doesn't make any sense! I have tried everything and can't figure out what is going on. In the new view, called ScreenView, it also has the same code in viewDidLoad:
self.contentSizeForViewInPopover = CGSizeMake(1000.0, 610.0);
An interesting observation that I've made but don't know what to do with is that if I take out the contentForSize code from the Second view in the tableView, or the one that I am trying to push to within the controller, when it pushes, it gets cut in half again, to the width of a standard popover controller you see in like Safari. But adding that code in makes it 2 times as wide, like there are two glued together or something, but it is still more narrow than the original width.
I don't know what's going on here, hopefully someone can help me out!
Thanks
Here is an image of the width it should stay:
And then here it is after it pushes the new view:
For each view you are presenting in the UIPopoverController, set the property
contentSizeForViewInPopover
For example:
myViewController.contentSizeForViewInPopover = CGSize(120.0f, 320.0f);
You will need to account for the size of the UINavigationBar (that you get from the UINavigationBar) inside the UIPopoverController but overall that controls the stretching and contracting of the UIPopoverController.
I wanted to add a view to the bottom of my screen. The controller is a UITableViewController, how do I shrink the tableView and add a extra view at the bottom of the tableview?
I've tried setting the frame of self.tableView in different places (viewDidLoad, viewWillAppear etc) but nothing happens. The tableView is created by IB and not programtically.
I've tried added a footer to my table view but that's not what I want, because the footer actually scrolls up, I want a static non moving View at the bottom of the screen.
I'm not saying you can't do it otherwise, but you may not want a UITableViewController for this situation. You can still have your view controller implement UITableViewDelegate and UITableViewDataSource, but place a vanilla UIView in your nib, into which you place a UITableView. Then just make sure to set the view outlet to the UIView containing your table. This has the effect of allowing you to create your additional view within IB. I just tried this and it appeared to work.
I'm guessing you're using a UINavigationController. When you push a controller onto your navigation stack, UINavigationController resizes its view to full screen, ignoring the geometry and autoresizing behavior you've defined in IB.
This resizing seems to happen after viewWillAppear:. In the past I've had some success resizing a table view and adding a sibling view in viewDidAppear:, after calling [super viewDidAppear:]. This is a bit risky though, since Apple could break it by changing how UINavigationController works behind the scenes.
A safer option is to push a view controller onto your navigation stack that controls a wrapper view. Then add your UITableView and its sibling as subviews of that wrapper view. The annoying thing about this option is that you'll probably want to use a nested UITableViewController to manage your non-full screen table view, but the documentation for UIViewController says it's designed to manage full screen views only. If you decide to ignore this admonition and nest your view controllers anyway, you'll find that viewWill/DidAppear/Disappear don't get called on the nested controller, so you'll have to manually delegate those methods from your wrapper view controller. This lack of support for nested controllers is one of my biggest pet peeves about UIKit, and I've gone to great lengths to engineer around it.
If you want to toe the line and use view controllers only for full screen views, you can push a normal view controller that controls your full screen wrapper view, manually implement all the UITableViewDataSource and UITableViewDelegate methods in your view controller, and set it as the delegate for your table view.
you want to change the -loadView method. Not viewDidLoad or viewWillAppear. This will allow you to make additional configurations with your tableview even if it is created in IB.
- (void)loadView {
[super loadView];
CGRect titleRect = CGRectMake(0, 0, 300, 40);
UILabel *tableTitle = [[UILabel alloc] initWithFrame:titleRect];
tableTitle.textColor = [UIColor blueColor];
tableTitle.backgroundColor = [self.tableView backgroundColor];
tableTitle.opaque = YES;
tableTitle.font = [UIFont boldSystemFontOfSize:18];
tableTitle.text = [curTrail objectForKey:#"Name"];
self.tableView.tableHeaderView = tableTitle;
[self.tableView reloadData];
[tableTitle release];
}
I don't know how to do it in IB but the way to do it in code is with this:
- (void) loadView
{
UITableView *tv = [[UITableView alloc] initWithFrame: rect
style: UITableViewStyleGrouped];
// finishg configuring table view
self.view = tv;
[tv release];
}
Trying to do it in two stages -- style first and then frame or frame first and then style -- neither of them works.