I have a universal single view app which has a UINavigationBar toolbar at the bottom, then the UIViewController view takes up the rest of the space.
I usually do everything programmatically, so for the longest time I've been calculating the UIViewController's view frame manually in loadView, taking into account all of the components that a device could have i.e. home indicator height on new iPhone device, opaque status bar on old devices.
However, with all the new devices I'm finding this way to be inefficient and have thought about switching to a storyboard or xib. I've never really worked with these so I don't understand how it works. For instance, if I create a storyboard with the ViewController's view taking the entire size of the usable area, will this translate for all devices? Will it resize correctly when there's rotation?
In general I'm kind of wondering, what is the most elegant / efficient way to create an app where the UIViewController's view takes up the entire usable area of the screen on all devices, including when the phone rotates?
I usually do everything programmatically, so for the longest time I've been calculating the UIViewController's view frame manually in loadView
There was never any need to do that; you can just delete that code. The navigation controller itself will give its child view controller's view the correct frame!
All you have to do is provide the child view controller's views subviews with correct positioning. And that is something you should be doing just by giving them autolayout constraints, so that they will be correctly arranged no matter what size the view controller's view ends up being. You can do that in code or through a storyboard; it makes no difference.
In general I'm kind of wondering, what is the most elegant / efficient way to create an app where the UIViewController's view takes up the entire usable area of the screen on all devices, including when the phone rotates?
The elegant efficient way is: do nothing. That is what already happens, automatically, with no input from you.
I was using a xib to do some simple view layouts and noticed that the size of my subviews was incorrect in relation to the size of the view itself.
I decided the last time I tried to ask this question I may have already had too complicated of a product for people to fully understand what I was asking.
Here is a simple view:
It is exactly as I would like it to be position and exactly how I would have expected to lay itself out as shown here:
If you notice at the bottom there, I did a log of the size of the views. The log is as follows in my viewDidLoad lifecycle method.
NSLog(#"%f, %f", [[UIScreen mainScreen] bounds].size.width, self.blockView.bounds.size.width);
Why is it that the view width is in no way close to the actual width reported my the application's view size? I am trying to cast a shadow under my subview but since programmatically it thinks the view is much wider, this wont work. Also positioning any subviews programmatically in the purple view shown there will not work either since the frame does not match the expect frame.
PLEASE HELP.
thank you.
EDIT: LACK OF SLEEP LEADS TO FORGETTING LIFE CYCLES....
You should log the view width in viewDidAppear.
viewDidLoad is called after the view controller has loaded its view
hierarchy into memory.
By the time viewDidLoad is called, auto layout hasn't finished calculating your view position according to the constraints of the view.
This is in continuation to the problem I had here(which is still unresolved): link
But this may help understand what is the problem.
I created just a simple test project ('Empty Application') and added a view controller with a XIB file (check box: 'With XIB file for user interface' selected). Code looks like this:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"didLoad: %#",NSStringFromCGRect(self.view.bounds));
// Do any additional setup after loading the view from its nib.
}
-(void) viewDidAppear:(BOOL)animated
{
NSLog(#"didAppear: %#",NSStringFromCGRect(self.view.bounds));
}
This is the output:
2013-07-26 17:05:28.502 testtest[5926:c07] didLoad: {{0, 0}, {320, 548}}
2013-07-26 17:05:28.506 testtest[5926:c07] didAppear: {{0, 0}, {320, 460}}
How come they are different?
(ps. I am testing on 6.1 simulator)
When the viewDidLoad method is called, your view controller has only just been loaded from your storyboard or XIB, and so the view dimensions are equal to those that you have in the XIB (those looks like iPhone 5 height dimensions).
Later, when viewDidAppear: is called, the view has already appeared on the screen, so it has been resized appropriately to actually fit on the screen, so its dimensions may be different to those in your storyboard, and consequently different to those that are set when the view is loaded.
In your case, it looks like your storyboard or XIB file is set to iPhone 5 screen size (548 = 1136/2 - status bar), and you are testing in a pre-iPhone 5 simulator or device with a 480x320 point screen, so the view gets resized down to 460 points high to fit on the screen.
This could have perfect sense.
ViewDidLoad is called lazily when first access of controller.view, so by that time the frame is not set yet. This means that you can not rely on the frame/bounds sizes at this point because it will only contain a default value (although in many cases it will be correct).
In ViewDidAppear, the frame is usually set, although if your parent controller is setting any animation you could also have a temporal frame state instead of the final one, but it is not usual as by convention this method is called when the view is already displayed.
For example, if you are loading the view from an IB file, the frame you will get in the viewDidLoad is the one you have in the IB file, but maybe the final size for your view is smaller/bigger, and then you will get another one in your viewDidAppear.
Instead of that, you should create all your elements resizable (use Spring&Struts, AutoLayout or any other similar alternative) so they will be properly displayed when the frame is set.
When a ViewController presents its view, it normally shrinks that
view so that its frame does not overlap the device’s status bar.
So when you NSLog in viewDidLoad, the View i not yet loaded so ViewController has not shrinked the frame yet but in viewDidAppear , it has done the resizing.
There is a property in UIViewController
wantsFullScreenLayout
Setting this property to YES causes the view controller to size its
view so that it fills the entire screen, including the area under the
status bar. (Of course, for this to happen, the window hosting the
view controller must itself be sized to fill the entire screen,
including the area underneath the status bar.) You would typically set
this property to YES in cases where you have a translucent status bar
and want your view’s content to be visible behind that view.
As far as i know, ViewDidLoad will set the bounds for your application as defined, in the RootViewController/XIB-file for root view, or maybe the AppDelegate.
If you define the applications bounds there (not sure in which one of these), ViewDidLoad functions in the entire app, will initially, set the height according to that.
Once the View is loaded, and ready to 'appear', actual bounds may be requested.
Hence it is advised to request bounds/sizes in the ViewWillAppear/ViewDidAppear methods.
-viewDidLoad is called the first time viewController.view is called. It is called before the view is returned. This has a very important implication. In order for the view to be shown or sized, it needs to be added to the window or some other view.
How does this happen? It looks something like [window addSubview:viewController.view].
So, the fully loaded view is needed before it can be placed into the view hierarchy. This places limitations on self.view when used within -viewDidLoad.
self.view.superview and self.view.window will always be nil.
self.view is not yet sized to fit into self.view.superview.
With this understanding, you can see how self.view cannot know it's final size when -viewDidLoad is called.
UPDATE
If you need to apply a custom layout to a view's subviews, you may want to consider subclassing UIView and using -[UIView layoutSubviews].
Barring that, are you having trouble with -viewWillAppear: instead of -viewDidAppear:? -viewWillAppear: is called before the view is displayed, so you won't have odd visual effects when you resize the view.
Have a very large program where there is always a superview that just encompasses a custom segment controller. This view sits at the top of screen and controls navigation in several ways.
So the problem arose in only a selected few view controllers where everything was 100% programmaticly created. Essentially CGRect are not being defined in the property dynamic coordinates. But are not being recalculated on orientation change. Does anyone have a simple way to control this in the subview? I'm about to code something in the superview to pass to orientation to other subviews.. but there has to be a better way. Ideas?
Couple of pointers:
You can use auto-resizing masks to determine what happens to your views when their bounds change (ie, when the orientation changes). So UIViewAutoresizingFlexibleWidth means your view will 'stretch' proportionally with the superview when the bounds are changed. UIViewAutoresizingFlexibleLeftMargin means your view will effectively be right-aligned, as the left margin will adjust according to the width, etc etc.
Sometimes auto-resizing masks aren't enough - perhaps you have to change the view's content on an orientation, or do a complex animation. In this case, you use the willAnimateRotationToInterfaceOrientation method in your view controller. Your subviews might have a custom adjustForOrientation method that you've written that you can trigger when willAnimateRotation... is called.
Finally, on iOS 5 you can actually nest view controllers inside of view controllers, in which case orientation events get passed through automatically...but this is probably needlessly complex for what you're trying to do.
When I use self.view.frame.size.height, it gets the height of the child nib, rather than the height of how the view is going to fit into the overall layout. I get the same value using self.view.bounds.size.height and super.view.bounds.size.height.
Any help would be appreciated.
Ask at the right time. Not until the view is ready to appear has iOS figured out what size it will actually be. Move your code that depends upon the view being the right size to viewWillAppear instead of viewDidLoad or an init method.
In Xcode 4 you would select your view controller that contains the UIScrollView and set the 'Simulated Metrics' in the IB Attributes Inspector as follows:
This ensures that your UIScrollView will be set to the correct size to fill the available space at design time. It looks like you are using an older version of Xcode/IB and I can't remember exactly where these settings used to be but they're in there somewhere.
With the layout designed to accommodate the space taken up by the tab bar you should be able to query the size of the UIScrollView at any time and get the correct values.