A guide to loading nibs
This article gives a deep dive on how to load nibs of custom views in your apps and explains why the described steps are necessary.
How to load nibs?
Before I go deeper into why certain steps are necessary, I want to quickly explain how you can load nibs of custom views.
Add a new UIView subclass, ViewFromNib
, as well as a UIView .xib
to your project. Then, add the following code to ViewFromNib
in order to load the corresponding .xib
:
let bundle = Bundle(for: ViewFromNib.self)
let className = String(describing: ViewFromNib.self)
let nib = UINib(nibName: className, bundle: bundle)guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else {
fatalError("Failed to load nib for view \\\\(className).")
}
Step by step:
Bundle(for: ViewFromNib.self)
= returns the bundle ofViewFromNib
. The corresponding.xib
should be in that same bundleString(describing: ViewFromNib.self)
= returns the name of your UIView subclass as a StringUINib(nibName: className, bundle: bundle)
= returns the decoded.xib
with the given name in the given bundle. For convenience and readability the.xib
and the UIView subclass should have the same name.nib.instantiate(withOwner: self, options: nil)
= creates new instances of the loaded nib's contents and sets the given owner, in this case an instance ofViewFromNib
, as the File's Owner of all created instances.instantiate
returns an array of typeAny
. This array represents all top-level elements (usually views) in the loaded.xib
. Therefore we need to specify which of those top-level views we want to set as a subview. This is why we call.first
to get the first top-level view and cast it into aUIView
.
On a side note: In your .xib
you can either set the File's Owner type to your UIView subclass or leave it blank (I‘ll explain later). On the other hand, you must not set the top-level view's type to the type of your UIView subclass (I’ll also explain this later).
What else you need to do:
Just loading the .xib
is not enough though. We also need to add this loaded view as a subview to ViewFromNib
:
view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(view)
Where to put it:
This code should be added to both initialization methods of ViewFromNib
:
init(frame: CGRect)
init(coder: NSCoder)
This way you ensure that the .xib
is always loaded when your view is instantiated. It then does not matter if you instantiate your view in your code or use it in another .xib
.
Since we use the exact same code in both init-methods, it is a good idea to create a new method e.g. loadView()
where the .xib
is loaded and added as a subview. You can then just call this method in your init
-methods instead. Or, to take it a step further, you can create a subclass of UIView that wraps the loading of a the .xib and let CustomView inherit from it.
That’s it. You’re good to go. If you want to have more background information on why you got to do this continue reading…
…Okay…but why?
First it is important to understand what is happening when we use a custom view in our app. Here it is necessary to distinguish between using your custom view in another .xib
and instantiating it programmatically.
But to really understand the difference and to also get an idea of why we need to load the view’s .xib
and set it as the view's subview, you need to understand what is happening when a .xib
is loaded.
What happens when a .xib
is loaded (in short)
- The whole content of a
.xib
file as well as all referenced resources are loaded. - It sends
init(coder: NSCoder)
(objects that conform toNSCoding
) /init()
(all other objects) messages to all of its objects. - All connections like actions, outlets and bindings are established between the loaded objects and the
owner
passed when loading the.xib
.
Outlet connections: usessetValue:forKey:
method to connect all outlets
Action connections: usesaddTarget:action:forControlEvents:
method - It sends
awakeFromNib()
messages to all objects that were created usinginit(coder: NSCoder)
- It displays any windows whose ‘visible at launch time’ attribute was enabled in the nib file
By knowing this, these three things become clearer
awakeFromNib()
is only called wheninit(coder: NSCoder)
was called. Therefore, we should not add any setup code that should always be run in theawakeFromNib()
method- When a
.xib
is loaded and instantiated like I described earlier it tries to find a corresponding outlet property in its File's Owner for its objects that have an outlet set. This is why it is crucial to set the instance of the UIView subclass as the nib's File's Owner when callinginstantiateNib(withOwner:options:)
. Otherwise the nib is not able to connect the outlet and the famous "this class is not key value coding compliant" error occurs. On the other hand, it is not necessary to set the type of the File's Owner in the Interface Builder. - When you load a
.xib
, an instance of the top-level view is returned. So when you also set the type of the top-level view to the type of the nib's File's Owner. Or said differently, when the top-level view's type and File's Owner's type are equal, loading the.xib
always results in aBAD EXCESS ERROR
. This is because the.xib
's File's Owner cannot be an instance of its top-level view.
So what is a File’s Owner (in short)
- It is one of the most important objects in a
.xib
- It is the main link between the application code and the contents of the
.xib
file - It is like a controller object that is responsible for the contents of the
.xib
file - It is the single point-of-contact for anything outside of the
.xib
file
What happens when a view controller with a custom view as a subview is loaded?
Let’s assume you added a view controller and its .xib
to your app. And you then added a view element to the view controller's view in its representing .xib
and set its type to your custom view. When you run your app and load this view controller, the view controller loads the nib as explained above. This means that an instance of your custom view is created by calling its init(coder: NSCoder)
which itself loads its corresponding .xib
and adds the .xib
's first top-level view as a subview to itself. So since we set the view controller's view's subview type to our custom view the .xib
of the custom view is now visible when the view controller's view is displayed.