Load view from an external xib file in storyboard
My full example is here, but I will provide a summary below.
Layout
Add a .swift and .xib file each with the same name to your project. The .xib file contains your custom view layout (using auto layout constraints preferably).
Make the swift file the xib file's owner.
Add the following code to the .swift file and hook up the outlets and actions from the .xib file.
import UIKitclass ResuableCustomView: UIView { let nibName = "ReusableCustomView" var contentView: UIView? @IBOutlet weak var label: UILabel! @IBAction func buttonTap(_ sender: UIButton) { label.text = "Hi" } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) guard let view = loadViewFromNib() else { return } view.frame = self.bounds self.addSubview(view) contentView = view } func loadViewFromNib() -> UIView? { let bundle = Bundle(for: type(of: self)) let nib = UINib(nibName: nibName, bundle: bundle) return nib.instantiate(withOwner: self, options: nil).first as? UIView }}
Use it
Use your custom view anywhere in your storyboard. Just add a UIView
and set the class name to your custom class name.
For a while Christopher Swasey's approach was the best approach I had found. I asked a couple of the senior devs on my team about it and one of them had the perfect solution! It satisfies every one of the concerns that Christopher Swasey so eloquently addressed and it doesn't require boilerplate subclass code(my main concern with his approach). There is one gotcha, but other than that it is fairly intuitive and easy to implement.
- Create a custom UIView class in a .swift file to control your xib. i.e.
MyCustomClass.swift
- Create a .xib file and style it as you want. i.e.
MyCustomClass.xib
- Set the
File's Owner
of the .xib file to be your custom class (MyCustomClass
) - GOTCHA: leave the
class
value (under theidentity Inspector
) for your custom view in the .xib file blank. So your custom view will have no specified class, but it will have a specified File's Owner. - Hook up your outlets as you normally would using the
Assistant Editor
.- NOTE: If you look at the
Connections Inspector
you will notice that your Referencing Outlets do not reference your custom class (i.e.MyCustomClass
), but rather referenceFile's Owner
. SinceFile's Owner
is specified to be your custom class, the outlets will hook up and work propery.
- NOTE: If you look at the
- Make sure your custom class has @IBDesignable before the class statement.
- Make your custom class conform to the
NibLoadable
protocol referenced below.- NOTE: If your custom class
.swift
file name is different from your.xib
file name, then set thenibName
property to be the name of your.xib
file.
- NOTE: If your custom class
- Implement
required init?(coder aDecoder: NSCoder)
andoverride init(frame: CGRect)
to callsetupFromNib()
like the example below. - Add a UIView to your desired storyboard and set the class to be your custom class name (i.e.
MyCustomClass
). - Watch IBDesignable in action as it draws your .xib in the storyboard with all of it's awe and wonder.
Here is the protocol you will want to reference:
public protocol NibLoadable { static var nibName: String { get }}public extension NibLoadable where Self: UIView { public static var nibName: String { return String(describing: Self.self) // defaults to the name of the class implementing this protocol. } public static var nib: UINib { let bundle = Bundle(for: Self.self) return UINib(nibName: Self.nibName, bundle: bundle) } func setupFromNib() { guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") } addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true }}
And here is an example of MyCustomClass
that implements the protocol (with the .xib file being named MyCustomClass.xib
):
@IBDesignableclass MyCustomClass: UIView, NibLoadable { @IBOutlet weak var myLabel: UILabel! required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupFromNib() } override init(frame: CGRect) { super.init(frame: frame) setupFromNib() }}
NOTE: If you miss the Gotcha and set the class
value inside your .xib file to be your custom class, then it will not draw in the storyboard and you will get a EXC_BAD_ACCESS
error when you run the app because it gets stuck in an infinite loop of trying to initialize the class from the nib using the init?(coder aDecoder: NSCoder)
method which then calls Self.nib.instantiate
and calls the init
again.
Assuming that you've created an xib that you want to use:
1) Create a custom subclass of UIView (you can go to File -> New -> File... -> Cocoa Touch Class. Make sure "Subclass of:" is "UIView").
2) Add a view that's based on the xib as a subview to this view at initialization.
In Obj-C
-(id)initWithCoder:(NSCoder *)aDecoder{ if (self = [super initWithCoder:aDecoder]) { UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename" owner:self options:nil] objectAtIndex:0]; xibView.frame = self.bounds; xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self addSubview: xibView]; } return self;}
In Swift 2
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView xibView.frame = self.bounds xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] self.addSubview(xibView)}
In Swift 3
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView xibView.frame = self.bounds xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.addSubview(xibView)}
3) Wherever you want to use it in your storyboard, add a UIView as you normally would, select the newly added view, go to the Identity Inspector (the third icon on the upper right that looks like a rectangle with lines in it), and enter your subclass's name in as the "Class" under "Custom Class".