Avoid Storyboards and Interface Builder
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.
Before you start lighting up the torches: yes, I have used Interface Builder before, extensively. Actually, up until a…medium.com
For iOS development, I don't use Interface Builder. I haven't willfully used a NIB (when I say NIB, I mean Interface…blog.teamtreehouse.com
What's the best way to design a UI in iOS: using Storyboards, NIBs, or code? A top iOS developer shares his thoughts in…www.toptal.com
This article was originally posted on my blog.medium.com
On Zeplin’s macOS app, over the past few months, we’ve started developing new features without using Interface Builder…blog.zeplin.io
Interface Builder Constraints Are Not Declarative
If you have used Interface Builder for Auto Layout, you know that the constraints on a view are buried in a hierarchical tree that lets you navigate the view controllers and views of your user interface.
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:
Cartography - A declarative Auto Layout DSL for Swift :iphone::triangular_ruler:github.com
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
The XML in XIBs was never meant to be human-readable and, as such, Xcode doesn’t care about making changes to it and will do so at a seemingly random whim. I call these changes 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:
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
On every project I’ve worked on in the last decade, code reviews are a major part of the creation and acceptance of source code. Many projects require multiple code review sign offs before integrating new code into the production code base.
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:
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:
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
I don’t know any professional programmers who don’t use source control and I’ve never been on a team which didn’t use source control. As of late, Git is the current source control favorite for its ultra-easy management of branches. However, when you have multiple branches and multiple developers, merge conflicts eventually occur.
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
Swift is a fantastic language. Unwrapping optionals and the associated use of guard let and if let, along with other language features, make it much easier to avoid errors in your code. But if you ever needed a clean, technical reason to avoid Interface Builder it is how Xcode and Interface Builder work with Swift.
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:
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
For views using Auto Layout, the best way to animate those views is to animate the view’s constraints. To animate a constraint, first you have to have a variable which references the constraint you want to animate. In code, this is trivial. But with constraints that are created in Interface Builder, you have to first define the variable, mark it as an IBOutlet, and then go into Interface Builder to connect the constraint to the IBOutlet.
The Bleeding Edge is Unproductive
As many iOS developers will remember, Interface Builder is one of the buggiest parts of Xcode during the months leading up to a new release of iOS. New features of iOS — UICollectionView when it was new, for example — are typically unsupported or buggy as hell in Interface Builder right up until that version of iOS (and the corresponding Xcode update) are released by Apple to production.
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
Unless your app is relatively simple, eventually you will encounter a scenario which will require laying out views and writing constraints in code. To this day there are still complex, dynamic views and animations which cannot be accomplished except through writing code:
What cannot be done with XIBs and Storyboards can always be implemented with code.
Coding Is Faster and Facilitates Reuse
As an iOS developer, how many times have you created an instance of UITableView and hooked up its associated UITableViewDelegate and UITableViewDataSource implementations? Personally, since 2008, I’ve created hundreds of these.
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.
TextExpander lets you instantly insert snippets of text from a repository of emails, boilerplate and other content, as…textexpander.com
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
Let’s look at the trivial example of creating a single UIView and a single UILabel subview with the centered text “foo”. For our purposes, let’s pin the label to its superview with a 30 point margin on all sides. To accomplish this in Interface Builder, the process looks like this:
Upon saving this view, Xcode creates a XIB which looks like this:
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()
// 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.
Ditch the storyboards and XIBs and embrace creating and laying out your user interfaces in code. Embrace the Auto Layout DSL options available and create all of your constraints programmatically.
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:
In my last blog post, I listed some of the reasons why I stopped using storyboards. If I’ve managed to convince you…medium.com