How to load a UIView using a nib file created with Interface Builder

Viewed 307082

I'm trying to do something a bit elaborate, but something that should be possible. So here is a challenge for all you experts out there (this forum is a pack of a lot of you guys :) ).

I'm creating a Questionnaire "component", which I want to load on a NavigationContoller (my QuestionManagerViewController). The "component" is an "empty" UIViewController, which can load different views depending on the question that needs to be answered.

The way I'm doing it is:

  1. Create Question1View object as a UIView subclass, defining some IBOutlets.
  2. Create (using Interface Builder) the Question1View.xib (HERE IS WHERE MY PROBLEM PROBABLY IS). I set both the UIViewController and the UIView to be of class Question1View.
  3. I link the outlets with the view's component (using IB).
  4. I override the initWithNib of my QuestionManagerViewController to look like this:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    

When I run the code, I'm getting this error:

2009-05-14 15:05:37.152 iMobiDines[17148:20b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "Question1View" nib but the view outlet was not set.'

I'm sure there is a way to load the view using the nib file, without needing to create a viewController class.

24 Answers

Thank you all. I did find a way to do what I wanted.

  1. Create your UIView with the IBOutlets you need.
  2. Create the xib in IB, design it to you liking and link it like this: The File's Owner is of class UIViewController (No custom subclass, but the "real" one). The File Owner's view is connected to the main view and its class is declared as the one from step 1).
  3. Connect your controls with the IBOutlets.
  4. The DynamicViewController can run its logic to decide what view/xib to load. Once its made the decission, in the loadView method put something like this:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;
    

That's it!

The main bundle's loadNibNamed method will take care of initializing the view and creating the connections.

Now the ViewController can display a view or another depending on the data in memory, and the "parent" screen doesn't need to bother with this logic.

You should not be setting the class of your view controller to be a subclass of UIView in Interface Builder. That is most definitely at least part of your problem. Leave that as either UIViewController, some subclass of it, or some other custom class you have.

As for loading only a view from a xib, I was under the assumption that you had to have some sort of view controller (even if it doesn't extend UIViewController, which may be too heavyweight for your needs) set as the File's Owner in Interface Builder if you want to use it to define your interface. I did a little research to confirm this as well. This is because otherwise there would be no way to access any of the interface elements in the UIView, nor would there be a way to have your own methods in code be triggered by events.

If you use a UIViewController as your File's Owner for your views, you can just use initWithNibName:bundle: to load it and get the view controller object back. In IB, make sure you set the view outlet to the view with your interface in the xib. If you use some other type of object as your File's Owner, you'll need to use NSBundle's loadNibNamed:owner:options: method to load the nib, passing an instance of File's Owner to the method. All its properties will be set properly according to the outlets you define in IB.

The previous answer does not take into account a change in the NIB (XIB) structure that occurred between 2.0 and 2.1 of the iPhone SDK. User contents now start at index 0 instead of 1.

You can use the 2.1 macro which is valid for all version 2.1 and above (that's two underscores before IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

We use a technique similar to this for most of our applications.

Barney

Related