How to embed a custom view xib in a storyboard scene?

You're almost there. You need to override initWithCoder in your custom class you assigned the view to.

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
    }
    return self; }

Once that's done the StoryBoard will know to load the xib inside that UIView.

Here's a more detailed explanation:

This is how your UIViewController looks like on your story board: enter image description here

The blue space is basically a UIView that will "hold" your xib.

This is your xib:

enter image description here

There's an Action connected to a button on it that will print some text.

and this is the final result:

enter image description here

The difference between the first clickMe and the second is that the first was added to the UIViewController using the StoryBoard. The second was added using code.


You need to implement awakeAfterUsingCoder: in your custom UIView subclass. This method allows you to exchange the decoded object (from the storyboard) with a different object (from your reusable xib), like so:

- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
    // without this check you'll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
    // set the view tag in the MyView xib to be -999 and anything else in the storyboard.
    if ( self.tag == -999 )
    {
        return self;
    }

    // make sure your custom view is the first object in the nib
    MyView* v = [[[UINib nibWithNibName: @"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];

    // copy properties forward from the storyboard-decoded object (self)
    v.frame = self.frame;
    v.autoresizingMask = self.autoresizingMask;
    v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
    v.tag = self.tag;

    // copy any other attribtues you want to set in the storyboard

    // possibly copy any child constraints for width/height

    return v;
}

There's a pretty good writeup here discussing this technique and a few alternatives.

Furthermore, if you add IB_DESIGNABLE to your @interface declaration, and provide an initWithFrame: method you can get design-time preview to work in IB (Xcode 6 required!):

IB_DESIGNABLE @interface MyView : UIView
@end

@implementation MyView

- (id) initWithFrame: (CGRect) frame
{
    self = [[[UINib nibWithNibName: @"MyView"
                            bundle: [NSBundle bundleForClass: [MyView class]]]

             instantiateWithOwner: nil
             options: nil] firstObject];

    self.frame = frame;

    return self;
}

A pretty cool and reusable way of doing this Interface Builder and Swift 4:

  1. Create a new class like so:

    import Foundation
    import UIKit
    
    @IBDesignable class XibView: UIView {
    
        @IBInspectable var xibName: String?
    
        override func awakeFromNib() { 
            guard let name = self.xibName, 
                  let xib = Bundle.main.loadNibNamed(name, owner: self), 
                  let view = xib.first as? UIView else { return }    
            self.addSubview(view)
        }
    
    }
    
  2. In your storyboard, add a UIView that will act as the container for the Xib. Give it a class name of XibView:

  3. In the property inspector of this new XibView, set the name of your .xib (without the file extension) in the IBInspectable field:

  4. Add a new Xib view to your project, and in the property inspector, set the Xib's "File's Owner" to XibView (ensure you've only set the "File's Owner" to your custom class, DO NOT subclass the content view, or it will crash), and again, set the IBInspectable field:

One thing to note: This assumes that you're matching the .xib frame to its container. If you do not, or need it to be resizable, you'll need to add in some programmatic constraints or modify the subview's frame to fit. I use snapkit to make things easy:

xibView.snp_makeConstraints(closure: { (make) -> Void in
    make.edges.equalTo(self)
})

Bonus points

Allegedly you can use prepareForInterfaceBuilder() to make these reusable views visible in Interface Builder, but I haven't had much luck. This blog suggests adding a contentView property, and calling the following:

override func prepareForInterfaceBuilder() {
    super.prepareForInterfaceBuilder()
    xibSetup()
    contentView?.prepareForInterfaceBuilder()
}