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, CustomView, as well as a UIView .xib to your project. Then, add the following code to CustomView in order to load the corresponding .xib:

let bundle = Bundle(for: CustomView.self)
let className = String(describing: CustomView.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: CustomView.self) returns the bundle of CustomView. The corresponding .xib  should be in that same bundle
  • String(describing: CustomView.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 CustomView, 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. On the other hand, you must not set the top-level view's type to the type of your UIView subclass.

   

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 CustomView :

view.frame = self.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(view)

 

Where to put it:

This code should be added to both initialisation methods of CustomView :

  • 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 .

public class CustomView: UIView {
    public override init(frame: CGRect) {
        super.init(frame: frame)

        let bundle = Bundle(for: CustomView.self)
        let className = String(describing: CustomView.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).")
        }

        view.frame = self.bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.addSubview(view)
    }

    public override required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        let bundle = Bundle(for: CustomView.self)
        let className = String(describing: CustomView.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).")
        }

        view.frame = self.bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.addSubview(view)
    }
}

 

Since we use the exact same code in both initialisers, 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 initialisers 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...

 

Why do we need to do all this?

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 owner passed 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