Raphael Cruzeiro

Why I prefer code over the Interface Builder

Like many of the iOS (and macOS) developers today, I began developing for Apple devices by following Apple’s own tutorials. I still remember how magical it was, back in 2011 and coming from a background of Python and C++, to open a .xib file and the corresponding class on the assistant editor and just drag from one file to the other to create IBOutlets and IBActions. 'This is great', I thought. It was really refreshing to be relieved of boilerplate code and focus only on what my app should do.

It didn’t take long though, and I began seeing the downsides of relying on such contraption. Specially when I moved from working on my small educational projects and began working on larger projects with other developers.

Merging hell

Both .xib and Storyboards are not meant to be human readable. You are supposed to work with the nice graphical representation of those files that Xcode generates for you. This means that if you and your coworker happen to be doing work on the same file, one of you will have to deal with solving merge conflicts later on. A lot of the times, merging will involve choosing changes randomly and then opening the file on Xcode and figuring out what needs to be done to ensure that the file is in the state it needs to be.

Last but not least: Xcode will alter the Interface Builder files just by opening them. Sometimes the slightest modification results in massive changes to the file (this is because the XML nodes of the file are represented as a set in memory and sets do not guarantee order). Other times Xcode will just change the syntax of the .xib for the fun of it.

Why all these meaningless changes from just opening the file?

Tight coupling with (almost) no compile time safety

Have you ever ran your app, with confidence that it would work as expected, just to see it crash once you open a certain view controller? And upon checking what exactly the crash was, figured that it was an IBOutlet that was not connected? Yeah, we’ve all been there.

Moreover, Interface Builder files are harder to reuse and even harder to refactor. It’s almost impossible to get a single look at a file like that and have a good idea of everything that that file is defining.

I’ve seen just about everything when it comes to Interface Builder woes. From files that would suddenly make Xcode crash just from opening a certain file to a view controller that could be instantiated with different xibs and when instantiated with one of these xibs could lead to a crash because, wait for it, an IBOutlet didn't have the correspondent element on that xib and since Xcode generates the IBOutlets as implicitly unwrapped optionals, any attempt to access that variable would cause the app to crash.

Dragging and clicking things is not my strong suit

There are small things that make the task of adding a constraint through the Interface Builder an infuriating task. Like having to move to the next layout constraint field so that Xcode will not ignore my constraint when I click Add Constraint, the time it takes for Xcode to open a single Storyboard, or being in the middle of editing something and having Xcode crash on me (and then having to reopen the project and figure out what was the last change that got saved).

When UI work is done with code, it’s easier to figure out exactly what is happening or what do I need to change in that code that was written one year ago to implement that new feature. Specially if the code was written with consideration for good practises, everything will be explicit and I will know exactly where to look. This is something that reminds me of the joys of the years I spent working with Python: Explicit is better than implicit.

But writing auto layout code is so verbose and tedious…

There are a few frameworks that take away the pain of writing auto layout code. My framework of choice is Cartography. Cartography makes writing auto layout code extremely simple and intuitive. Just check out this example from the project’s README:

constrain(view) { view in  
    view.width  == 100
    view.height == 100
}

let group = ConstraintGroup()

// Attach `view` to the top left corner of its superview
constrain(view, replace: group) { view in  
    view.top  == view.superview!.top
    view.left == view.superview!.left
}

/* Later */

// Move the view to the bottom right corner of its superview
constrain(view, replace: group) { view in  
    view.bottom == view.superview!.bottom
    view.right  == view.superview!.right
}

UIView.animateWithDuration(0.5, animations: view.layoutIfNeeded)

It can't get any simpler than that. Plus, it's always a joy to be able to see and quickly understand everything my coworker did during a code review:

Much better

Did you see that? Constants! Sweet constants!

There will always be those cases that you feel at a loss on how to implement something with auto layout. For those cases I can tell you this: there’s nothing wrong with overriding layoutSubviews and calculating your frames there. Don't ever let someone tell you otherwise. Always favor the simplest solution to a problem.

Tagged with: