Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct loadView implementation

Apple's docs do not say what the correct implementation is for loadView.

I've found that if you implement loadView like this:

- (void)loadView
{
    self.view = [[UIView alloc] init];
}

...then you get different behaviour than if you don't implement it at all. In particular, in one 20-line project, I've found that viewWillAppear is called with a zero-size frame for self.view - unless you use Apple's default version of loadView.

Looking on Google, there are lots of "tutorials" that provide obviously-wrong loadView implementations - e.g. force-setting the size to (320,480) because the tutorial author "found that it works if I do this".

I'd like to know what the correct implementation should be.

NB: in my example above, I'm adding it to the view hierarchy inside AppDelegate like this:

[self.window addSubview:(UIViewController*).view];

I believe that in the presence of a UINavigationController or UITabBarController, Apple does some extra magic that - as a side-effect - causes a one-line loadView implementation to work OK. But I want to write it correctly, so that it always works!

NB: I've tried setting the autoresizing mask on the root view, but it doesn't change what happens:

- (void)loadView
{
    self.view = [[UIView alloc] init];
    self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
like image 821
Adam Avatar asked Mar 21 '12 11:03

Adam


2 Answers

First, there is no 'default' implementation of loadView...that method is specifically there for you to override. I do agree that Apple's docs can be a little unclear though. But loadView is called by default whenever the view of the navigation controller is accessed and no view exists (for example: UIView *view = viewController.view). It can also be called manually. But in no situation will loadView have the correct dimensions...that is, in fact, impossible. loadView is called in order for the parent view controller to get the view in the first place so it can size it appropriately. Then once it gets the view it calls viewDidLoad. This is the only code path they can use because views can load from the loadView method or the nib and they must provide a place for additional setup when views are loaded from a nib. Finally, the parent controller will resize the view and call viewWillAppear only when the view will actually appear. For example, if you push a controller on a navController that's off screen, it won't call viewWillAppear until the navController itself is placed on screen. This is done because there's no point in running that code until the controller is actually visible. This is also why you can only ever get the correct dimension in the viewWillAppear method.

Now, you noticed that if you add a controller to a standard controller none of this stuff happens. This is because view controllers aren't really intended to contain other view controllers per say. Now in iOS 5, they explicitly support the use of Container View Controllers...which is essentially a view controller that IS designed to contain other view controllers. They added a few 'convenience' methods in iOS 5 to help with this but it's not strictly necessary. The jist of all this is: if you want to add one view controller to another, you will have to manually setup all the appropriate calls to the child view controller (all loading methods, rotation events, memory warning etc). In other words, you have to make your own container view controller. When you do, though, keep in mind what I said before about the code path. It's important that you call child controller methods in the same order Apple does or stuff won't work right.

Here's some links to info: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html -Scroll down to: Implementing a Container View Controller

Also here for the view controller life cycle, which will help you figure out which calls need to be made in which order: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW1

I do recommend reading the entire View Controller Programming Guide....you can gleam a lot of information from there: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007457-CH1-SW1

like image 44
Aaron Hayman Avatar answered Sep 28 '22 07:09

Aaron Hayman


Default implementation of -loadView creates the view or loads NIB. As far as I know, there is no way to know the final size of the view at time of creation in -loadView. So the default view size is set to UIScreen.mainScreen.bounds. This is because it may be difficult to work with zero frame view in -viewDidLoad and other methods.

Your one-line implementation may look like this:

- (void)loadView {
    self.view = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds];
}

You don't need to set the autoresizing mask, because you don't know in what context the view will be displayed. The caller is responsible to set you correct frame, autoresizing mask and similar properties.

Imagine this in a UINavigationController method:

// we are pushing new VC, view is accessed for the first time
pushedVC.view.frame = CGRectMake(...);

It is setting the correct frame, but your -loadView is called just before that -setFrame:. So during -viewDidLoad you have temporary non-zero frame, just to be able to setup subviews and internal autoresizing. After this, the correct frame is set to you and in -viewWillAppear: you have final frame.

like image 99
Tricertops Avatar answered Sep 28 '22 05:09

Tricertops