Correct way to load a Nib for a UIView subclass

104,176

Solution 1

MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

I'm using this to initialise the reusable custom views I have.


Note that you can use "firstObject" at the end there, it's a little cleaner. "firstObject" is a handy method for NSArray and NSMutableArray.

Here's a typical example, of loading a xib to use as a table header. In your file YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Normally, in the TopArea.xib, you would click on File Owner and set the file owner to YourClass. Then actually in YourClass.h you would have IBOutlet properties. In TopArea.xib, you can drag controls to those outlets.

Don't forget that in TopArea.xib, you may have to click on the View itself and drag that to some outlet, so you have control of it, if necessary. (A very worthwhile tip is that when you are doing this for table cell rows, you absolutely have to do that - you have to connect the view itself to the relevant property in your code.)

Solution 2

If you want to keep your CustomView and its xib independent of File's Owner, then follow these steps

  • Leave the File's Owner field empty.
  • Click on actual view in xib file of your CustomView and set its Custom Class as CustomView (name of your custom view class)
  • Add IBOutlet in .h file of your custom view.
  • In .xib file of your custom view, click on view and go in Connection Inspector. Here you will all your IBOutlets which you define in .h file
  • Connect them with their respective view.

in .m file of your CustomView class, override the init method as follow

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

Now when you want to load your CustomView, use the following line of code [[CustomView alloc] init];

Solution 3

Follow the following steps

  1. Create a class named MyView .h/.m of type UIView.
  2. Create a xib of same name MyView.xib.
  3. Now change the File Owner class to UIViewController from NSObject in xib. See the image below enter image description here
  4. Connect the File Owner View to your View. See the image below enter image description here

  5. Change the class of your View to MyView. Same as 3.

  6. Place controls create IBOutlets.

Here is the code to load the View:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

Hope it helps.

Clarification:

UIViewController is used to load your xib and the View which the UIViewController has is actually MyView which you have assigned in the MyView xib..

Demo I have made a demo grab here

Solution 4

Answering my own question about 2 or something years later here but...

It uses a protocol extension so you can do it without any extra code for all classes.

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()

Solution 5

In Swift:

For example, name of your custom class is InfoView

At first, you create files InfoView.xib and InfoView.swiftlike this:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

Then set File's Owner to UIViewController like this:

enter image description here

Rename your View to InfoView:

enter image description here

Right-click to File's Owner and connect your view field with your InfoView:

enter image description here

Make sure that class name is InfoView:

enter image description here

And after this you can add the action to button in your custom class without any problem:

enter image description here

And usage of this custom class in your MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
Share:
104,176
Adam Waite
Author by

Adam Waite

I make iOS apps in East London. I make web apps occasionally too, mainly with Rails and JS.

Updated on July 08, 2022

Comments

  • Adam Waite
    Adam Waite almost 2 years

    I am aware this question has been asked before but the answers are contradicting and I am confused, so please don't flame me.

    I want to have a reusable UIView subclass throughout my app. I want to describe the interface using a nib file.

    Now let's say it's a loading indicator view with an activity indicator in it. I would like on some event to instantiate this view and animate in to a view controller's view. I could describe the view's interface no problem programmatically, creating the elements programmatically and setting their frame inside an init method etc.

    How can I do this using a nib though? Maintaining the size given in interface builder without having to set a frame.

    I've managed to do it like this, but I'm sure it is wrong (it's just a view with a picker in it):

     - (id)initWithDataSource:(NSDictionary *)dataSource {
            self = [super init];
            if (self){
                self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
                self.pickerViewData = dataSource;
                [self configurePickerView];
            }
            return self;
        }
    

    But I'm overwriting self, and when I instantiate it:

    FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
        selectView.delegate = self;
    
        selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);
    

    I have to manually set the frame rather than have it picked up from IB.

    EDIT: I want to create this custom view in a view controller, and have access to control the view's elements. I don't want a new view controller.

    Thanks

    EDIT: I Don't know if this is best practice, I'm sure it's not, but this is how I did it:

    FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
        selectView.delegate = self;
        [selectView configurePickerViewWithData:ds];
        selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
        selectView.alpha = 0.9;
        [self.view addSubview:selectView];
        [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                                selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                                selectView.alpha = 1;
                            } completion:^(BOOL finished) {
                            }];
    

    Correct practice still wanted

    Should this have been done using a view controller and init with nib name? Should I have set the nib in some UIView initialisation method in the code? Or is what I have done ok?

  • meronix
    meronix about 11 years
    there's no initWithNibName: method for a UIView, it's just for UIViewController
  • Adam Waite
    Adam Waite about 11 years
    That's for a view controller, I want to use a UIView and have the controller that creates it to own it.
  • Adam Waite
    Adam Waite about 11 years
    Why are people down voting this, wrong? I've not tried yet, but this looks like it wont do what I want.
  • Premsuraj
    Premsuraj about 11 years
    I'm sorry, I copied the wrong code in my haste, I've updated the answer, please have a look.
  • Adam Waite
    Adam Waite about 11 years
    Ok, so basically, best practice states that every UIView should have an associated controller?
  • Rakesh
    Rakesh about 11 years
    Not necessarily. But when loading a view from a separate xib that would be the problem-free way. Pre-IOS5 apple docs says one view controller should not be used to control more than one scene (xib) and vice-versa.
  • Rakesh
    Rakesh about 11 years
    I had down-voted, but thats cause I mis-read your answer (thought you were assigning a UIView subclass as the file's owner). Sorry about that.
  • Adam Waite
    Adam Waite about 11 years
    I'm going to mark this correct. I don't know if it's best practice but it works.
  • Adam Waite
    Adam Waite about 11 years
    The xib is actually only the size of an alert view
  • Rakesh
    Rakesh about 11 years
    I personally feel, it depends on the complexity of code/xib whether you have to create a new controller or not. If you can handle the problems caused successfully and if you are not looking at future modifications it's not a problem. But creating a separate view controller would solve a lot of problems for you. And since in your case its just a activity indicator you can use a UIViewController(as mentioned in answer) object easily. I'll update my answer to do this accordingly.
  • Rakesh
    Rakesh about 11 years
    Anyway if it's just a small view(logically and physically) you can put in the main view itself. Changing the class of your view to the custom class should take care of your problems, right? What I am trying to say is: if it's not big enough to take care using another controller why separate it in the first place.
  • Adam Waite
    Adam Waite about 11 years
    I don't think that's right. The view controller that was created is redundant. You are only making use of it's view property.
  • Rakesh
    Rakesh about 11 years
    Redundant because you are not using the view controller at all. Yes. But then why separate it into a different xib? :)
  • Adam Waite
    Adam Waite about 11 years
    So I can draw the interface in interface builder rather than doing it programmatically. It's not a problem doing it programmatically, I just want to know how to subclass a UIView and give it a nib! This shouldn't be hard.
  • Rakesh
    Rakesh about 11 years
  • LightningStryk
    LightningStryk about 10 years
    In your xib file ignore the files owner. Then you can avoid having to create a UIViewController by just doing a MyView *view = [[UINib instantiateWithOwner:nil options:nil] lastObject];
  • iphonic
    iphonic about 10 years
    @LightningStryk Yes of course, but it just another way to do it.
  • arielyz
    arielyz about 10 years
    The is to create a reusable UIView subclass, not to use a UIViewController for that purpose.
  • iphonic
    iphonic about 10 years
    @arielyz See the answer properly, its an other way to do it.. By the way I am not using UIViewController but accessing the view attached to it.
  • user1686342
    user1686342 over 8 years
    @Joe Blow Sorry for the bump, but why would one have to do this: "Don't forget that in TopArea.xib, you may have to click on the View itself and drag that to some outlet, so you have control of it, if necessary." Could you instead just use myViewObject to access the underlying view as it is a UIView itself? Why is there a need for a property?
  • Fattie
    Fattie over 8 years
    Umm .. I don't know :) I forget .. nobody has used nib views for years. Go to container views! stackoverflow.com/a/23403979/294884 As you suggest it could be I confused view controllers with views, while writing there. Thanks for the point...
  • lifjoy
    lifjoy over 8 years
    As of iOS 9, this is the only solution listed on this page which actually works for me (the other solutions cause crashing).
  • Shamsiddin Saidov
    Shamsiddin Saidov over 7 years
    I did the same, seems fine, but when I instantiate the instance variables in this method they are becoming nil. How to solve this problem?
  • Pierre
    Pierre almost 7 years
    Just note that this approach is not compatible with loading your custom view from an existing XIB since it does not call -init. Instead it will call initWithFrame: and -awakeFromNib. If you write the same code to load your view as in the -init method you will enter an infinite loop.