Advertisement
Advertisement


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


Question

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.

2014/03/27
1
241
3/27/2014 1:26:23 PM


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.

2014/01/19

I'm not sure what some of the answers are talking about, but I need to put this answer here for when I search in Google next time. Keywords: "How to load a UIView from a nib" or "How to load a UIView from an NSBundle."

Here's the code almost 100% straight up from the Apress Beginning iPhone 3 book (page 247, "Using The New Table View Cell"):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

This supposes you have a UIView subclass called Blah, a nib called Blah which contains a UIView which has its class set to Blah.


Category: NSObject+LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Swift Extension

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

And an example in use:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
2017/10/18

For all those that need to manage more than one instance of the custom view, that is an Outlet Collection, I merged and customized the @Gonso, @AVeryDev and @Olie answers in this way:

  1. Create a custom MyView : UIView and set it as "Custom Class" of the root UIView in the desired XIB; custom class

  2. Create all outlets you need in MyView (do it now because after point 3 the IB will propose you to connect outlets to the UIViewController and not to the custom view as we want); custom class outlet

  3. Set your UIViewController as "File's Owner" of the custom view XIB; enter image description here

  4. In the UIViewController add a new UIViews for each instance of MyView you want, and connect them to UIViewController creating an Outlet Collection: these views will act as "wrapper" views for the custom view instances; enter image description here

  5. Finally, in the viewDidLoad of your UIViewController add the following lines:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..
2012/09/24

I would use UINib to instantiate a custom UIView to be reused

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

Files needed in this case are MyCustomView.xib, MyCustomViewClass.h and MyCustomViewClass.m Note that [UINib instantiateWithOwner] returns an array, so you should use the element which reflects the UIView you want to re-use. In this case it's the first element.

2015/11/23

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.

2009/05/14

Source: https://stackoverflow.com/questions/863321
Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Email: [email protected]