Avoid Storyboards and Interface Builder

Charles Sieg
Jun 22, 2017 · 12 min read

I wrote my first line of Objective-C code a few weeks after Steve Jobs announced the first iOS SDK in spring of 2008. During that mad rush to be one of the first apps in the new App Store, a very beta version of Xcode was the only tool available to write code that ran on the iPhone. While it may be hard to believe now, that version of Xcode did not ship with Interface Builder. You literally had to create every view and view controller programmatically. There was no other option.

Being a GUI aficionado, I was looking forward to using Interface Builder to create beautiful new user interfaces for the iPhone. However, when Interface Builder did ship later that year, I tried it out, hated it, and have only looked back occasionally, usually when Apple has released some new hotness at the WWDC.

Through the advent of storyboards, Auto Layout, and other features, I’ve stayed with my programmatic approach and found it to be efficient, productive, and resilient across iOS releases. This topic has generated a lot of attention over the past couple of years. There are a number of excellent articles, similar to this one, that deride the use of Interface Builder and storyboards. I’m going to build a bit on some of their arguments.

Interface Builder Constraints Are Not Declarative

If you have a view with a lot of subviews and want to see all of the constraints involved, you have to expand all of the subviews and the Constraints folder under each one. To make matters worse, to see all of the details of a particular constraint, you have to click on the constraint to focus it and then review its properties in a separate utility panel.

Constraints in Interface Builder are:

  • Hidden until exposed.
  • Do not show complete information until focused.
  • Not grouped with constraints on other relevant views.

This is far from optimal.

Constraints created in code are the opposite. They are:

  • Easily grouped together with related constraints to get a holistic view.
  • Easily discovered and readable in (usually) a single file.
  • Declarative with all relevant properties set in the declaration.
  • Easily commented to explain the purpose or use of the constraint, if desired.

While creating Auto Layout constraints programmatically used to be tedious, there are now several excellent open source frameworks which provide a Domain-Specific Language (DSL) for easily creating Auto Layout constraints in Swift code. These include:

and my current favorite:

All of these are free to use and licensed under the flexible MIT license.

In lieu of utilizing one of these frameworks, constraints programmatically-created with the usual iOS APIs are still declarative, self-documenting, and easy to find and understand.

Xcode’s XIB Graffiti

graffiti: writings subversively scratched on a surface

This is a diff of changes that Xcode made to a storyboard file I opened recently. All I did was look at the storyboard in IB. I didn’t touch a single view, and yet Xcode made dozens of small changes throughout the file:

Image for post
Image for post
Xcode graffiti

There is no reason for these changes. For a supposedly diff-friendly format, this sort of noise just complicates the commit process. Sure, I will revert these changes before committing my code but I shouldn’t have to.

Interface Builder Files Are Not Reviewable

One of the red flags that is often raised during a code review is the use of a magic number — a hard-coded constant which provides little or no context as to its purpose or use. In a recent code review I was called out on the use of a magic number that I had included in one of my programmatic constraints. It was called out because it was right there in the code, easily caught by the reviewer.

However, what about magic numbers in constraints created in Interface Builder? A cursory review of storyboards in the same application yields hundreds of magic numbers:

Image for post
Image for post
Constraints in IB with Magic Numbers

Why are these not caught during code review? The reason is that no one reviews XIB files and, really, no one can. Again, they are managed by Xcode directly and most of the time, in addition to any meaningful changes, a XIB also contains hundreds of lines of graffiti as described earlier.

This means that XIBs, for all intents and purposes, are essentially useless in a code review and, as a result, all of the user interface details contained within go unreviewed. And even if you tried to review a XIB, it’s virtually impossible to call out an offending constraint because its only identifier is a random alphanumeric string assigned to it by Xcode and meaningless to the developer:

Image for post
Image for post
Xcode’s internal identifiers

The importance of this shortcoming cannot be overstated. GitHub is a favorite source control provider. We use it at Renkara and many of our clients use it. Most code reviews are performed on pull requests created on the GitHub website, which automatically generates a diff showing changes made to files in the pull request.

If you are using Interface Builder for creating your user interface, you are doing your team a huge disservice because an incredible amount of your work is not being reviewed because it is obfuscated in the mess of XML which is XIB.

Sure, the code reviewer could check out the branch associated with the PR, fire up Xcode, open up Interface Builder, and then manually expand and inspect all of the views and constraints to see if everything is as expected. This is utterly tedious but could be done. But what if there’s an issue that you want the developer to fix? How do you report that? You’re stuck going back to GitHub, crawling through the diff and finding the XIB or storyboard file that has the offending view in it and attach your feedback to that. Sooo inefficient and time-consuming.

Source Control Woes

In the early days, Interface Builder saved files in a binary format called NIB (NeXT Interface Builder). If you had a merge conflict in a NIB file, it was literally impossible to resolve. Someone’s work got in and someone’s work had to be reverted and reapplied.

A few years ago, Apple introduced a new XML format called XIB (Xml Interface Builder). A merge conflict in a XIB file (or a storyboard file, which is just a giant aggregation of XIBs) is no longer impossible to resolve, just time-consuming and annoying and usually the fastest resolution is the same as with NIBs: pick a winner and revert and reapply the loser.

There is another wrinkle in this, one that I’ve seen over and over again on projects. Git is a powerful source control tool and, many times, can resolve conflicts between files automatically. With source code, this automatic conflict resolution almost always works perfectly and is a huge time-saver. But let Git automatically resolves conflicts in XIBs and storyboards? Time after time, Git merges incorrectly and unexpected changes are quietly made. If you are lucky, these changes result in a corrupted file which Xcode will complain about and draw your attention to immediately. But frequently the mistake introduces a subtle defect that goes unnoticed for a long time. This is a nightmare scenario for most developers but, unfortunately, it happens all too often when using XIBs and storyboards.

No Compile Time Safety

Anyone who has used Swift with IB knows where I’m going with this. If you forget to connect an IBOutlet, your app crashes at runtime as soon as you touch it. In the type-safe world of Swift, this class of bug shouldn’t even exist. To make matters worse, Xcode automatically generates an IBOutlet as an implicitly unwrapped optional! Obviously, you can change this but for this to be the default, literally, WTF:

Image for post
Image for post
Courtesy of The Clean Swifter

This, of course, doesn’t happen if all of this is done in code. Instead, the compiler checks your work and ensures that the code is written so that a method is only called if the object can be unwrapped. The error is prevented before the app can even be compiled. No crash, no brainer. This, by itself, without any other reasons, should be enough to avoid IB, if you are writing Swift code.

A Pain to Animate

The Bleeding Edge is Unproductive

If you want to maximize your productivity, skip using Interface Builder to try out new iOS features and, instead, create them in code or, even better, code them in a Swift playground.

Coding is Inevitable

Image for post
Image for post
Preview Transition by Ramotion, Inc.

What cannot be done with XIBs and Storyboards can always be implemented with code.

Coding Is Faster and Facilitates Reuse

However, once you’ve created one, creating a new one takes about 30 seconds because of the marvelous invention called Copy & Paste. Simply create a new class file, find an existing implementation, copy it, and paste it in the new file, rename a few things and, done. Or, if you’re like me, you have all of these templates and snippets in TextExpander and can simply type your keyword (mine is “xtable”) and your templated UITableView magically appears.

Why is this faster? Because you can’t just copy and paste XIBs. If you are able to copy and paste a view inside a XIB, you still have to manually hook up the IBOutlets and IBActions(see the compile time safety issue above) and you still need to create the backing class implementation (which, to be fair, could be copied and pasted). In any case, if you have two developers side-by-side, one coding and one using IB, to see who gets a working table view controller app running first, the coder will win every time.

Putting It All Together

Image for post
Image for post
Creating constraints in Interface Builder

Upon saving this view, Xcode creates a XIB which looks like this:

Image for post
Image for post
XML for new UIView

The first thing to note in the resulting XML is that even though we know there are only two views — the UILabel and its superview — it is still difficult to review this file. Parsing through it, we can see that the view’s internal identifier is “iN0-l3-epB” and we can see that this identifier is the secondItem referenced in two of the constraints. However, the other identifier used “BAX-sL-1sP” cannot be seen. This is clearly the id attribute for the UILabel but its declaration is so far to the right that it gets cut off even when reviewing this in full screen mode on my 27" iMac screen.

Now let’s reproduce this in Swift:

let label = UILabel()
label.text = “foo”
label.textAlignment = .center
let view = UIView()
view.addSubview(label)
// UX wants a 30 point margin around the label.
let margin: CGFloat = 30.0
// Apply the constraint using TinyConstraints.
label.edges(to: view, insets: UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin))

I wrote this code in less than 60 seconds. These 7 statements produce exactly the same result as the Interface Builder version but are infinitely easier to read and understand. The views are created, properties are set, and all four constraints are applied programmatically by calling a self-documenting UIView extension method provided by TinyConstraints. There are even comments to explain things to developers and code reviewers.

Now, let’s say UX comes back and wants to change the margins around the side to 40 points instead of 30 points. With Interface Builder, I have to change the “30” to “40” in four places in our trivial example. Obviously, in more complex views, across entire storyboards, and even across multiple storyboards, the number of constants that need to be updated can increase significantly. Manually changing all of these constants is a tedious and error-prone process. With a programmatic solution, it can be a single constant in a single line of code.

Summary

Do this and you’ll immediately start seeing:

  • Improved developer productivity.
  • Improved code reuse.
  • No limits to what can be created.
  • Improved code reviews.
  • Easier animation.
  • Safer code with fewer crashes.
  • Easily resolved merge conflicts.

Once you go full code, you’ll never go back. Here are a few links to help you on your way:

Remote Technologist

An stream of opinionated techincal articles on…

Charles Sieg

Written by

Entrepreneur, animator, Tough Mudder, Texan. Needs little sleep. Prefers espresso, drives Porsches. Drinks Don Julio & craft cocktails. Eats lots of red meat.

Remote Technologist

An stream of opinionated techincal articles on productivity, iOS, Node, AWS, and other passions of mine.

Charles Sieg

Written by

Entrepreneur, animator, Tough Mudder, Texan. Needs little sleep. Prefers espresso, drives Porsches. Drinks Don Julio & craft cocktails. Eats lots of red meat.

Remote Technologist

An stream of opinionated techincal articles on productivity, iOS, Node, AWS, and other passions of mine.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store