iosdev

Interface Builder is declarative too. Where’s the love?

Interface Builder is an amazing tool, which allows you to declaratively build your UI. Same as SwiftUI — so why all the hate and ridicule over the years instead of unconditional love?

I’ve long been the proponent of using Interface Builder exclusively for the layout, in iOS apps. It always felt as the most natural way to build UI components: you tuck and nip stuff around while always having the result rendered in front of you. You can add any data you want, use any device size, test with CGSize.zero or .infinitum or whatever you want.

When you are working in IB, runtime is not involved and you need to think only about what data are getting in and how to present them. You describe the layout and let the framework render it in runtime based on that description / declaration.

This is the same thing that SwiftUI does. So why it seems that SwiftUI is more or less universally loved while IB was often ridiculed? I’ve come to conclusion is down to two factors.

Interface Builder is part of Xcode which is an IDE; a complex, multi-purpose programming tool that apart from allowing you to write your code, deals with a lot of other stuff you take for granted:

IDEs try to save their own state very often, in order to prepare for eventual crash and restart (every single complex app will eventually crash). IB is one module inside Xcode but which, as visual layout builder, has a whole heap of its own state on top of Xcode’s already large pile.

The first hard knock against IB was decision by Xcode creators to save parts of that state inside .storyboard or .xib file. So every time you touch anything in IB canvas, you’ll more than likely drag it a bit and Xcode will helpfully save that change of its canvas state, behind the scene.

The position and even size of any view inside IB canvas area is utterly irrelevant change for the component itself but nonetheless it altered the file, which leads you to commit that change into git repository (or discard changes every minute or so). Which leads to merge conflicts if at least two developers in the team position the said component differently on their IB canvas. Which happens every damn time as some devs use 13in MacBook and others use 27in iMacs so they need to shuffle stuff around to make the best use of the available screen estate.

For the life of me, I can’t understand why IB canvas state is not saved somewhere in the .xcuserdata folder, which is already added to everyone’s .gitignore file. But it is what it is and that boat has sailed.

The second knock against Interface Builder is the whole storyboard nonsense. It’s the worst idea ever brought into the UIKit. Just look at any storyboard-based app and count the UISegue hacks required to pass data around. It’s not possible to declare the data instances you want to pass through the app. Not even in small apps, certainly not in more complex one. Apple’s recommended approach seems to be: use IB to connect UIViewControllers but then switch to code editor – an entirely different mental context – to write String-based glue code to prepareForSegue and what not. I dread to think how much developer time was spent chasing that pipe dream, inside and outside of Apple.

When you couple these two factors together, it’s not hard to realize why all the hate and ridicule. Shared app screens in storyboards means multiple developers working on same storyboard file, having constant merge conflicts throughout each working day. It’s a nightmare honestly; it’s perfectly understandable that large teams abandoned usage of storyboards as preached by Apple.

The sad part is that usually meant abandoning Interface Builder entirely and doing everything in code. Which then lead to writing Auto Layout constraints in code, which is a special type of masochism in my book. Which lead to people hating Auto Layout too, because making sense of constraints in code requires excruciating levels of mental effort.

Sane approach to using Interface Builder:

  1. One UIVC per .storyboard file, to declare big-picture layout, usually to declare containers for minor, more specific components.
  2. Instantiate those UIVCs in code and inject their data dependencies at that very moment, when you need them.
  3. Each minor component’s layout is declared in its own .xib file.
  4. Load instances of each component in code and populate the containers in the UIVC.

This approach lets you use Interface Builder only for what it does best: declare the layout of single self-contained pieces of UI. Then in code, you implement data flow and behavior. That’s it; look at SwiftUI and Instant Previews – when you strip away the hype, that is what remains.

As you then extend your app and add new features, existing xibs and storyboards are rarely touched. Hence less maintenance, easier testing and more productive developer teams.