views:

576

answers:

2

A view controller FooViewController needs to do some initialization when it's created (of itself, not of the managed view - before the view is even loaded).

I want to be able to create it in two ways:

  1. Programatically, possibly with some arguments:

    FooViewController *fooViewController = [[[FooViewController alloc] initWithSomeData:data] autorelease];
    [self.navigationController pushViewController:fooViewController animated:YES];
    
  2. In Interface Builder.

For (1) I would write the initializers like this:

- (id)initWithSomeData:(Data *)data     // designated initializer
{
    if (self = [super initWithNibName:@"FooView" bundle:nil]) {
        self.title = "Foo";
        // perform initialization
    }
    return self;
}
- (id)init
{
    return [self initWithSomeData:nilOrSomeDefaultValue];
}

// ... other initializers that call the designated initializer

(I hardcode the nib name since the controller always uses the same view configuration and because which view it uses doesn't concern the callers at all)

When created in Interface Builder, I would want the end result be the same as if the object was initialized with the parameterless init.

Now if I just do:

- (id)initWithCoder:(NSCoder *)decoder
{
    return [self init];
}

The title, wantsFullScreenLayout and nibName properties which are set in the Inspector in Interface Builder would then be ignored, but that's OK. The nibName is hardcoded in init and title is set there in case the controller is instantiated programatically anyways.

The problem is that parentViewController isn't set (the default initWithCoder: would set it based on the hierarchy of objects in the NIB).

How can I get the parent object from the nib? I would then change initWithCoder: to something like:

- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [self init]) {
        _parentViewController = [decoder parentObject];
    }
    return self;
}

Or should I be using some different approach to create controllers which can be instantiated both programatically and in IB?

A: 

Don't, don't, don't try to make a viewcontroller that works with and without a nib. It will be a nightmare because nibloading uses some of the normal entry points and provides new ones, and it will be fragile with respect to OS revisions.

What you can do is it make the view controller always load from a nib, then give yourself a convenience initializer to go through the nib:

- (id) init {
  return [[[self class] alloc] initWithNibNamed:@"MyNibName" bundle:nil];
}

Then you can reference it through other nibs the normal way, and just call the convenience init method when you don't want to explicitly deal with the nib.

Louis Gerbarg
Is `[self class]` supposed to be just `self`?If so, you're not actually loading the view controller from NIB. UIViewController's nibName property is the NIB for it's view, not the controller itself.I don't think having two NIBs for each controller (one for itself, one for the view) is the way to go ... :/
Jaka Jančar
It is fixed, it is supposed to be [[self class] alloc] since I don't know what your class is. In order to do what I was suggesting you need to set the File's Owner to your class. What initWithNibNamed does is it initializes the view controller then binds all the connections to the nib into the VC you just initialized. It seems like you are trying to create a separate view controller object in the nib, how are you setting your File's Owner, I think you are fighting the system.
Louis Gerbarg
Ahh, I think you misunderstood the question... The view controller will *always* use a nib for it's own view. I always build views in IB. The difference is whether *the controller* object will be created programatically or in a nib.
Jaka Jančar
Okay, I think I see what you want to do, but you are not going to achieve it the way you are doing it. What you should do is setup an IBOutlet that the nib can set to the parent, connect it in the nib, and then it will be setup for you. The one catch is you need to call [super initWithCoder:] in your initWithCoder:. initWithCoder: is also a designated initializer, and going through the non-coder one can break connections the nib is trying to setup.
Louis Gerbarg
A: 

Why not do init stuff in viewDidLoad - when creating outside of IB, you can set initial values with some other methods or properties after initialization, but before viewDidLoad is called.

Kendall Helmstetter Gelner
Because viewDidLoad is meant for view initialization, not controller initialization. For example, it can be called multiple times in low memory situations when the view is released. Controller initialization should, obviously, be done in init.
Jaka Jančar
Or you check to see if initialization has been done already before you do it again in viewDidLoad. Since you obviously cannot pass custom parameters to init in view controllers instantiated from IB, you need some place that gets called in both the case of IB initialization and standard creation, with the option of passing in parameters (according to the original question being posed). viewDidLoad is a good candidate for that since you'll be doing view specific initialization as well. Otherwise you have a number of different init methods to confuse yourself with.
Kendall Helmstetter Gelner
Furthermore the use of viewDidLoad allows you to initialize a view controller, but set some parameters via properties before the view is actually loaded.
Kendall Helmstetter Gelner