A guide to loading nibs

The PM Booth
5 min readMar 1, 2021

--

Photo by Tobias Keller on Unsplash

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 of ViewFromNib. The corresponding .xib should be in that same bundle
  • String(describing: ViewFromNib.self) = returns the name of your UIView subclass as a String
  • UINib(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 of ViewFromNib, as the File's Owner of all created instances.
  • instantiate returns an array of type Any. 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 a UIView.

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)

  1. The whole content of a .xib file as well as all referenced resources are loaded.
  2. It sends init(coder: NSCoder) (objects that conform to NSCoding) / init() (all other objects) messages to all of its objects.
  3. All connections like actions, outlets and bindings are established between the loaded objects and the ownerpassed when loading the .xib.
    Outlet connections: uses setValue:forKey: method to connect all outlets
    Action connections: uses addTarget:action:forControlEvents: method
  4. It sends awakeFromNib() messages to all objects that were created using init(coder: NSCoder)
  5. It displays any windows whose ‘visible at launch time’ attribute was enabled in the nib file

By knowing this, these three things become clearer

  1. awakeFromNib() is only called when init(coder: NSCoder) was called. Therefore, we should not add any setup code that should always be run in the awakeFromNib() method
  2. 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 calling instantiateNib(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.
  3. 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 a BAD 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.

Where to go from here

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/Introduction/Introduction.html#//apple_ref/doc/uid/10000051i

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW8

--

--