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.
Solution 1:
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
- select the xib
- select 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
// 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
- Open your storyboard
- Add a view
- Set that view's Custom Class
- wait a sec ... BOOM!!
Solution 2:
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
- select the xib
- select file's owner
-
set custom class to 'ReusableView' in the Identity Inspector.
- 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
- Open Assistant Editor
- Control + Drag from the view to your interface
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;
}
Test your reuseable view in a storyboard
- Open your storyboard
- Add a view
- Set that view's Custom Class
Run and observe!
Solution 3:
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
Solution 4:
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]))
}
Solution 5:
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 :)