views:

4740

answers:

2

Summary: How should the UIViewController know the size of its UIView instance when initializing that view?

The dedicated initialization method for an UIView is the initWithFrame:(CGRect)frame method. This sets the frame for the newly created UIView. This method could be called from the UIViewController's loadView method, if that view controller's view is requested. The documentation of UIViewController states with respect to subclassing and view size:

When creating the views for your view hierarchy, you should always set the autoresizing properties of your views. When a view controller is displayed on screen, its root view is typically resized to fit the available space, which can vary depending on the window’s current orientation and the presence of other interface elements such as the status bar.

So, the UIViewController instance should set those properties. So far so good, the UIViewController so far does not have to know how big its view is or will be.

When the view of a UIViewController is requested and the view property is nil, the loadView method of the view controller is called. Now there is a problem, because the UIView needs to be initialized, but the view controller still does not know what size the view should be. How big should you initialize that view? Where in code do you determine the view size?

You could initialize the view with a zero rect (CGRectZero):

- (void)loadView {
    self.view = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
}

And let the caller set the view frame like so:

UIViewController *viewController = [[MyUIViewController alloc] init];
// next two lines are normally combined, but for clarity they are not now
UIView *view = viewController.view;
view.frame = CGRectMake(0, 0, 200, 200);

This requests the view from the view controller (viewController.view), and thus loads its view with the loadView method. This loadView method initializes the view with a CGRectZero. Then the caller sets its frame (view.frame = ...)

The thing is that the frame property on the view is set twice, possibly causing even more double work if your custom UIView is doing some advanced layout in the setFrame method (placing and resizing subviews for example). You could prevent this by creating a dedicated initializer method for the UIViewController which already asks the caller for a CGRect, which you would store in an ivar. At the time the loadView method is called, you use this ivar to create the view.

What is the good way to go here? Either setting the view's frame twice (initializing with CGRectZero, and setting afterwards), or giving the UIViewController a new initializer method with a CGRect (and thus giving it a frame property)? Or am I missing something and are there other possibilities?

+2  A: 

You don't have to use the designated initializer. Just use init as in [[UIView alloc] init]. The designated initializer has to be used from subclasses' initializers.

On the other hand, setting the frame twice should not do much harm. Performing a lot of tasks in setFrame: is unusual. Layouting is normally done in layoutSubviews and is only performed once.

Nikolai Ruhe
UIView's init method actually calls initWithFrame with a zero rect
drvdijk
I notice now, while applying a transform to a view, the layoutSubviews is called. Considering I'm applying the transform in the view controller's touchesMoved method, doing a lot of work in layoutSubviews would not be recommended either ...
drvdijk
`layoutSubviews` is called lazily (after `setNeedsLayout`) and only once for a bunch of changes. Therefore it is the best place to perform expensive resizing.
Nikolai Ruhe
+2  A: 

I'm currently trying to accomplish the same thing after subclassing from UIViewController to create a generic component and came to notice something quite weird :

UIViewController has a "loadView" method that you may override to (quoting documentation) "This is where subclasses should create their custom view hierarchy".

So, i create my view hierarchy, compositing my screen using a default frame size (since I may not be knowing the frame i should display things into at the time loadView is called).

But then, upon what call do I correctly set frame sizes ? Aka : at what time is my view controller aware of it's view real size ?

Do I have to create a "setFrame" in my ViewController subclass that the object creating the viewcontroller should call ? that seems weird.

drvdijk : did you have a solution to your problem ?

EDIT : just found this Note on UIViewController documentation : Note: You should not use view controllers to manage views that fill only a part of their window—that is, only part of the area defined by the application content rectangle. If you want to have an interface composed of several smaller views, embed them all in a single root view and manage that view with your view controller.

So, it seems that UIViewController main view is always full screen. Period.

That does pose a problem to me though, as I'm trying to create a custom UITabBarViewController (to be able to display custom images in the tabbar, and not blue gradient) : my tabBarViewController should displays subviewcontrollers into a part of the window only...

Ben G