Interface Builder (IB for short) was announced as a part of Xcode 4 in June 2010, at the WWDC. Since that moment, Apple has been promoting and improving it with each significant Xcode update. At the moment IB is a well-established solution which comes with every new iOS project created in Xcode. However… recently articles illustrating advantages of life without Interface Builder are becoming viral. Moreover, there is a growing number of job offers for devs familiarized with libraries like SnapKit or Cartography which enhance the process of setting up Auto Layout constraints from code. That shows that some developers are unhappy with IB and maybe they figured out a better solution. That pushed me to through and analyze common doubts related to IB and investigate if there are better alternatives to it. The most popular ones are described in this article.
Let’s imagine an application with all of its views designed in one Storyboard file. Interface Builder files like
.xib (descendant of
.storyboard are internally represented as painful to read XMLs. Even minor visual change might have a huge impact on the whole file structure, which might not be a problem… until you have to merge two changes like that together. As you might imagine, it makes cooperation much harder, especially in larger teams. Does it mean that IB should be avoided in complex applications? From my perspective, no.
If we think about code, it is natural to cut huge and scary classes into smaller pieces using different design and architecture patterns (e.g. MVVM). Complex classes are hard to extend and maintain. Because of the same reasons, we should cut SB files. A simple solution to merging problem (and many other problems) is the habit of cutting one application-wide Storyboard into several smaller pieces, each related to a separate business flow like login/register, send feedback or finalize a purchase. I usually try to limit the number of View Controllers in one file to five/six items, but often it is even one or two. From my experience, I can say that most of the time, one task is related to only one business flow. Such granulation allows developers to work simultaneously on the UI in different files without merge conflicts.
Smaller storyboards also make the projects structure more maintainable because files can be named in a meaningful way like
Address.storyboard which clearly indicates that all VCs associated with the address are there. To go one step further we could even put all relevant designs and classes in a folder named
It is possible to make an architectural mistake, especially when a project evolves with additional unplanned features. So what if we already made a giant and hairy storyboard? Fortunately, Xcode supports splitting it into smaller pieces. Just select all VCs you want (see picture below) to export to a new file and use Editor -> Refactor to Storyboard:
It will create for you a new Storyboard file with all selected items in it.
Also, your segues are still there, but they target a Storyboard Reference!
Cutting also helps to make development faster because massive SBs become painfully slow in Xcode.
Every elegant User Interface follows the basic principle of using a consistent theme all over the app. Most of us have to keep the same spaces, fonts, and colors of elements across different screens so if an application’s design is defined only in IB a developer usually must copy and paste UI items from one place to the another. Furthermore, if a color of one element changes, we need to go through the whole app and hunt for all instances to apply a new styling (happily Xcode 8 supports searching in IB files).
A simple solution to that inconvenience could be writing a custom class for reusable UI controls. Such class could define values of mutual appearance attributes. Here is an example of UIButton subclass:
We can create a button in IB, set up its constraints and title. To apply styling we need to specify the class in Identity Inspector. As
IBDesignable it will look in IB as we defined it in code:
An example of such architecture could be an extension to the UIView:
To manage colors we could use a struct containing all reusable colors:
Or more sophisticated solution like Chameleon lib.
Presented solution may not be sufficient in all scenarios. What if there are buttons that run the same action, but look different? That could be addressed by additional subclassing and protocols.
Another useful tool is UIAppearance protocol. It changes the appearance of controls globally. As an example, we can define the background and text color of
AppDelegate. Unfortunately, some things are not possible (e.g. rounding corners). Another limitation is that modifications made through UIAppearance are not reflected in IB (as it was in the case of
IBDesignable subclass). Here is how it can look like in practice:
In my opinion sticking to defining UI solely in Interface Builder is a good practice. It saves time for switching between files and keeps things organized and in one place. Unfortunately, it might be problematic as some important properties of
UIView are not exposed to Interface Builder. An excellent example is
CALayer. As a part of
Core Animation library, it allows manipulating on borders, corners, masks, shadows and much more.
To overcome that, we can use User Defined Runtime Attributes providing a mechanism for configuring any key-value coded property in IB which makes it a powerful tool. On the other hand, the type and value of an attribute need to be set separately for each item, without any autocompletion or hinting, which makes it clumsy.
IBInspectable properties solve this problem in an elegant way. Properties marked as inspectable are exposed to a developer in
Attributes Inspector. Another advantage is that modification made to
IBInspectable attribute of IBDesignable class will be visible in IB just after a change. For example, we can create an extension to
UIView or any its subclass to edit CALayer attributes:
Personally, I suggest adding inspectable properties directly to
UIView class carefully. They can quickly flood
Attributes Inspector so we will have to scroll through a long list of properties to find the one we are looking for.
Have you ever made a typo in a segue or reusable identifier name? I guess that not once or twice. The problem is identifiers are strings and there is no way to use variables with autocompletion instead. Oversights like these crash an app at runtime which makes IB less safe. After every modification we should run the app and check if everything is fine. If you make such mistakes in code, you catch them at compile time and fix quickly.
Fortunately, there is StoryboardLint which finds wrong classes and identifiers names. I am aware that it is another dependency to add to your project, but in my opinion, it is worth it. Just take a look at the following example:
Segues are one of the primary benefits of SBs. They eliminate redundant code needed to push and present view controllers and visualize relationships between them. Moreover, VCs are automatically created, so there’s no need to alloc and init manually. On the other hand, Storyboards take care of handling the transition between view controllers, but not the flow of data, so we have to accept a
prepareForSegue, with a stretched
Another problem is the navigation between any pair of screens. Let’s imagine that an app can receive push notifications from a server based on user’s actions. These notifications can redirect from any to any other view controller. In this case, instead of segues, I prefer code based approach. It not only resolves that problem but also reduces the size of
prepareForSegue. An example of simple architecture:
Such strategy allows for navigation as simple as:
Interface Builder still has many problems, yet it seems that Apple is doing its best fixing them in each new Xcode version. Personally, I am an enthusiast of Storyboards and XIBs mostly because they save time. Even if sometimes we have to combine them with code to produce ideal solutions they still reduce the effort for setting up auto layout constraints and default copy like buttons’ titles. Also, it’s worth noticing that if you join a project where all the screens are designed from code, it takes much more time to get the understanding of how things are working and which classes are associated with the particular screen. From my perspective, an additional advantage is that IB provides
preview feature which allows inspecting how a screen will look like at a chosen device and system version without compilation which dramatically speeds up the iterative design process. Especially in Swift.
As you may notice, some proposed solutions are a mixture of code and IB. We all know that there are no silver bullets, and good developers should look for solutions which are the most effective while using given tools. Both code and IB can be considered as tools, and it’s usually not a good idea to just throw something out the window because we don’t have a use for it yet or don’t like/understand it. Project-tailored solutions are the best ones, but unfortunately, they usually cost the most.