Adopting safer custom view initializers from Interface Builder.

Leo Kwan
Plated Engineering & Data Science
8 min readJul 18, 2018

--

Interface Builder and Storyboards are Apple’s current rendition of a WYSIWYG tool for UI layout on OS X and iOS. They get a lot of hate, especially when they’re in the context of anything but a pet project. In this post, I am going to try and shut that down, and layout some techniques to effectively use these tools in a large codebase, effectively leveraging when best to use code and when to represent views in a visual tool like IB (Interface Builder).

I’ve heard several reasons against Interface Builder. Some of those reasons are:

Interface Builder is:

1. evil.

2. not performant.

3. hard to maintain under multiple contributers

4. become bloated (storyboards specifically) as an application matures.

And most likely, someone like Crusty(the figurative old-wise-and-ancient programmer euphemized by Apple) would probably say:

Real programmers would never use interface builder!

I see where these hesitations come from — but I still like using Storyboards — under certain conditions. In my opinion, view controllers in a storyboard, or a single-unit views in xibs, will always get the view’s point across more effectively than lengthy files of layout code — frame-based, visual-format-language, manual NSLayoutConstraints; these are the myriad of ways to programatically write UI code natively. Note: Autolayout DSL libraries like SnapKit/Masonry are pretty nice though.

Manual view layout using Visual Format Language.

It’s also very likely that you’ll have to interact with a xib or storyboard when picking up on an existing codebase, so it’s always helpful to know your way around @IBOutlets and @IBActions.

In this post, I will outline some rules and conventions that I like to follow when leveraging interface builder in a large Swift codebase. will be divided into two parts: the first will focus on creating custom views, and the latter will layout how to do the same with view controllers in storyboards — both in interface builder.

#1 Break out custom views into nibs.

At Plated, we use storyboards pretty often. Storyboards serve as the defacto option for us when iterating on feature work quickly through it’s powerful WYSIWYG(What You See Is What You Get) workflow. But individual view elements don’t always live in the storyboard. Most of the time, views within our view controllers are reused, preventing us from baking a view directly onto a view controller. Instead, we’ll consolidate them into a separate ‘xib’ where we can just focus on how that one view should look.

What is a xib?

It stands for ‘Xcode Interface Builder’, but think of it as a file that describes user interfaces… built from the Interface Builder on XCode.

I might use the word xib and nib interchangeably in this post, because they are sort of the same thing, but nibs actually predate xibs. Ask someone who has actually worked with nibs back in the day to explain its differences. Or google it.

#1B. Call your xib file and class file the same name within the same bundle.

Following up on that, Rule 1b would be to call your xib file and class file the same name and within the same bundle (just have them next to each other). This assumption will allow us to eventually write slicker abstractions for creating nibs from code and from interface builder.

Place your xib and implemenation together for organization and convention.

If we assume nibs and their respective implementation files are the same name, just with different extensions (like .xib and .swift), we can extend our custom views that have corresponding xibs with an extension like HasNib below. String(describing:self) will evaluate to the file name self belongs to, so we must follow the same naming conventions between our .xib and .swift files for this extension to work. Rest assured, this will allow us initialize views from interface builder more flexibly and safely.

#1B. Construct your xibs such that they can embed onto other views in Interface Builder.

The main reason we extract a custom view’s code into a separate xib file is reusability.

So… if I am trying to make the case for using interface builder at scale, I should expect the ability to just directly embed xib-based views onto another view controller I’m working on in a storyboard. That way, I can either initialize my view programmatically, or I can decide to throw it onto Interface Builder and hook it up via @IBOutlet.

Surprisingly enough, this capability actually requires jumping over several hurdles, but doing so will ultimately enable the utmost flexibility between writing programattic & visual layouts.

Embedding a view from a .xib file into another view controller in Interface Builder.

If we want to do what is illustrated above, we cannot set our root view above in Interface Builder to our class file, EmbeddableCustomView. We should leave the root view blank and instead, set our class file as the view’s File’s Owner.

You may have seen ‘File’s Owner’ in the top left panel of your.xib files under the Placeholders section, highlighted in yellow:

File’s Owner is the proxy object between your interface builder layout and application code. It should sound like a view controller.

Think of the File’s Owner as an object you can declare to serve as a proxy between your .xib UI elements and your application code at runtime. If you’re really curious, you can check out the official docs to try and better understand this concept: Apple Docs. If it feels a little confusing even after reading it, you’re not alone.

Here’s the official definition of a File’s Owner:

Unlike interface objects, the File’s Owner object is a placeholder object that is not created when the nib file is loaded. Instead, you create this object in your code and pass it to the nib-loading code. The reason this object is so important is that it is the main link between your application code and the contents of the nib file.

Why do we need to know about the ‘File’s owner’ in order to nest xibs onto other xibs/view controllers?

To answer that, it might help to understand how nibs are initialized from a .xib and rendered onto memory— which is more eloquently documented by Apple. Simply put, loading a nib is divided into two parts. The first part is creating an instance of our nib from the nib file’s location. The second part is loading the contents of the nib(our custom view object graph) onto memory.

Creating the nib instance.

Going back to our HasNib protocol mentioned earlier, we see that our nib is initialized through the UINib API. In our example, we use the UINib(nibName:bundle:) initializer.

return UINib(nibName: String(describing: self), bundle: Bundle(for: self))

The nibName is the name of our custom view file(e.g. “EmbeddableCustomView”) and the bundle is the bundle EmbeddableCustomView.xib is stored in.

What is a bundle in OS X/iOS?

In OS X and iOS, a bundle is just a directly of files “bundled” together (executable code, resources[images, sounds], plists). When running your main application, you’ll notice you can access your main bundle, which show all the resources within the current executable, like your xib files. For the curious, check out what’s in your main bundle when starting up your application. If you have xibs in your project, they will be there.

let mainBundleContents = try! FileManager.default.contentsOfDirectory(at: Bundle.main.bundleURL, includingPropertiesForKeys: [], options: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles)

Unarchiving the contents of our nib.

So we now have a reference of the nib that represents EmbeddableCustomView; like I mentioned above, that’s one half of the battle. The second half is loading that representation onto memory. Apple documentation and Cocoa will refer to this process as “unarchiving” (the opposite operation of converting our view back to data is archiving).

High-level description of object unarchiving. We want to focus on File (our .xib file) -> Object

This process should sound familiar; in other object-oriented languages and parts of Apple’s Foundation framework, this pattern is called deserialization/serialization. Unarchiving a nib means reconstructing the relationships and dependencies of the nib’s represented object graph— exactly the way it was configured via Interface Builder.

let customViewNib = UINib(nibName: "EmbeddableCustomView", bundle: Bundle.main)
let customView = customViewNib.instantiate(withOwner: nil, options: nil).first as? EmbeddableCustomView

Calling instantiate(withOwner:options:) unarchives and loads all root level views from your nib file onto memory. Each xib file should only have one root view (I can’t think of a reason why there should be anything other than your custom view in your dedicated .xib file) so we can assume .first will cast into our desired view.

Hopefully, you’re beginning to see why embedding a custom view onto a storyboard will not automagically work. We need explicitly unarchive our view with the UINib API, like we did above by calling instantiate(withOwner:options:). Unfortunately, there’s no convenient way around having to manually unarchive nibs representing UIViews via Interface Builder.

Note: There are for UIViewControllers though 😉.

Okay, back to the concept of File’s Owner.

Storyboards will not unarchive our embedded views for us, we’ll need to do that ourselves. Instead of embedding our custom view onto interface builder, we can instead embed a proxy object of type, UIView, which can load, unarchive, and present our custom view for us. We just need to hook into every one of our proxy’s inititiazers to perform this nib-content loading process. In short, we’re going to try our best to abstract the unarchiving of our desired nib view within this proxy object, or File Owner.

I snagged this snippet of code from a popular repository called Reusable, but the repo is so small (but helpful) that you can just extend the original ‘HasNib’ extension.

Let’s continue with our example file EmbeddableCustomView and see how we can set EmbeddableCustomView as the file owner, and not the root view’s type.

The main abstraction lies in the loadNibContent() extension. It will load our nib’s root-level views(again, there should only be one if you’re doing things right), and manually adds edge layout constraints between your proxy UIView object and the actual custom view.

When it’s all said and done, the shell implementation of your custom view should look look something like this.

This method is one effective way to initialize a interface-builder based view in a flexible manner. On top of that, we can also initialize this view in code with an concise signature:

Before

let nib = UINib(nibName: String(describing: self), bundle: Bundle(for: self))
let customViewFromXib = nib.instantiate(withOwner: nil, options: nil).filter({ $0 is EmbeddableCustomView}).first as? EmbeddableCustomView

After

let customViewFromXib = EmbeddableCustomView()

or embedded in interface builder:

Here is the sample code project that reflect the examples above: https://github.com/leojkwan/safer-initializers-IB/tree/master

Hopefully, this post was informative in debunking how nibs actually work. In part 2, I will discuss how we can better initialize view controllers from interface builder, also by leveraging strict conventions and simple class extensions.

--

--