Reuse a uiview xib in storyboard

I typically like to create and design my uiviews in interface builder. Sometimes I need to create a single view in a xib that can be reused in multiple view controllers in a storyboard.

36400 次浏览

NEW! updated answer with ability to render directly in the storyboard (and swift!)

Works in Xcode 6.3.1

Create a new UIView named 'ReuseableView'

  • File > New > File > Source > Cocoa Touch Class > UIView

Create a matching xib file named 'ReuseableView'

  • File > New > File > User Interface > View

Set the file owner of the of the xib

  1. select the xib
  2. select file's owner
  3. set custom class to 'ReusableView' in the Identity Inspector. enter image description here

    • Note: Do not set the custom class of the view on the xib. Only the File Owner!

Make an outlet from the view in the ReuseableView.xib to your ReuseableView.h interface

  1. Open Assistant Editor
  2. Control + Drag from the view to your interface

enter image description here

Add initWithCoder implementation to load view and add as a subview.

- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {


// 1. load the interface
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
// 2. add as subview
[self addSubview:self.view];
// 3. allow for autolayout
self.view.translatesAutoresizingMaskIntoConstraints = NO;
// 4. add constraints to span entire view
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];


}
return self;
}

enter image description here

Test your reuseable view in a storyboard

  1. Open your storyboard
  2. Add a view
  3. Set that view's Custom Class

enter image description here

Run and observe! enter image description here

The initWithCoder function in swift 2 if anybody is having trouble translating it:

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
UINib(nibName: String(self.dynamicType), bundle: NSBundle.mainBundle()).instantiateWithOwner(self, options: nil)
self.addSubview(view)
self.view.translatesAutoresizingMaskIntoConstraints = false
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterY , metrics: nil, views: ["view": self.view]))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterX , metrics: nil, views: ["view": self.view]))
}

Reuse and render a xib in a storyboard.

Tested with Swift 2.2 & Xcode 7.3.1

1 ---- Create a new UIView named 'DesignableXibView'

  • File > New > File > Source > Cocoa Touch Class > UIView

2 ---- Create a matching xib file named 'DesignableXibView'

  • File > New > File > User Interface > View

3 ---- Set the file owner of the of the xib

  1. select the xib
  2. select file's owner
  3. set custom class to 'DesignableXibView' in the Identity Inspector.

Setting a Xib's Owner to a Custom Class

  • Note: Do not set the custom class of the view on the xib. Only the File Owner!

4 ---- DesignableXibView's Implementation

//  DesignableXibView.swift


import UIKit


@IBDesignable


class DesignableXibView: UIView {


var contentView : UIView?


override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}


required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}


func xibSetup() {
contentView = loadViewFromNib()


// use bounds not frame or it'll be offset
contentView!.frame = bounds


// Make the view stretch with containing view
contentView!.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]


// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(contentView!)
}


func loadViewFromNib() -> UIView! {


let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView


return view
}


}

5 ---- Test your reuseable view in a storyboard

  1. Open your storyboard
  2. Add a view
  3. Set that view's Custom Class
  4. wait a sec ... BOOM!!

Xib Rendering Inside a Storyboard View Controller

Swift 3&4 Update to the accepted answer

1. Create a new UIView named 'DesignableXibView'

  • File > New > File > Source > Cocoa Touch Class > UIView

2. Create a matching xib file named 'DesignableXibView'

  • File > New > File > User Interface > View

3. Set the file owner of the of the xib

Select the "DesignableXibView.xib" > "File's Owner" > set "Custom Class" to 'DesignableXibView' in the Identity Inspector.

  • Note: Do not set the custom class of the view on the xib. Only the File Owner!

4. DesignableXibView's Implementation

    import UIKit


@IBDesignable


class DesignableXibView: UIView {


var contentView : UIView!


override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}


required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}


func xibSetup() {
contentView = loadViewFromNib()


// use bounds not frame or it'll be offset
contentView.frame = bounds


// Make the view stretch with containing view
contentView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]


// Adding custom subview on top of our view
addSubview(contentView)
}


func loadViewFromNib() -> UIView! {


let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView


return view
}
}

5 Test your reusable view in a storyboard

  • Open your storyboard

  • Add a view

  • Set that view's Custom Class

If someone is interested, here's the Xamarin.iOS version of the code Step 4 of @Garfbargle

public partial class CustomView : UIView
{
public ErrorView(IntPtr handle) : base(handle)
{


}


[Export("awakeFromNib")]
public override void AwakeFromNib()
{
var nibObjects = NSBundle.MainBundle.LoadNib("CustomView", this, null);
var view = (UIView)Runtime.GetNSObject(nibObjects.ValueAt(0));
view.Frame = Bounds;
view.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;


AddSubview(rootView);
}
}

Here's the answer you've wanted all along. You can just create your CustomView class, have the master instance of it in a xib with all the subviews and outlets. Then you can apply that class to any instances in your storyboards or other xibs.

No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself.

Just do this:

  1. Import BFWControls framework
  2. Change your superclass from UIView to NibView (or from UITableViewCell to NibTableViewCell)

That's it!

It even works with IBDesignable to render your custom view (including the subviews from the xib) at design time in the storyboard.

You can read more about it here: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

And you can get the open source BFWControls framework here: https://github.com/BareFeetWare/BFWControls

Tom 👣

For anyone trying to adapt accepted answer (by @Garfbargle) to Objective-C

Just converting Swift to Objective-C isn't enough to make it work. I've had hard time to allow live rendering in Storyboard.

After translating the whole code, the view is well loaded when running on device (or simulator), but the live rendering in Storyboard doesn't work. The reason for this is that I used [NSBundle mainBundle] whereas Interface Builder hasn't got access to mainBundle. What you have to use instead is [NSBundle bundleForClass:self.classForCoder]. BOOM, live rendering works now !

Note : if you have having issues with Auto Layout, try disabling Safe Area Layout Guides in the Xib.

For your convenience, I leave my whole code here, so that you just have to copy/paste (for all process, follow original answer) :

BottomBarView.h

#import <UIKit/UIKit.h>


IB_DESIGNABLE
@interface BottomBarView : UIView


@end

BottomBarView.m

#import "BottomBarView.h"


@interface BottomBarView() {
UIView *contentView;
}


@end




@implementation BottomBarView


-(id) initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self xibSetup];
}
return self;
}


-(id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self xibSetup];
}
return self;
}


-(void) xibSetup {
contentView = [self loadViewFromNib];


contentView.frame = self.bounds;


contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;


[self addSubview:contentView];
}


-(UIView*) loadViewFromNib {
NSBundle *bundle = [NSBundle bundleForClass:self.classForCoder]; //this is the important line for view to render in IB
UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:bundle];
UIView *view = [nib instantiateWithOwner:self options:nil][0];


return view;
}


@end

Tell me if you encounter some issues but it should almost work out of the box :)