Superpower of Interface Builder no one talks about

Building dynamic UI with nibs and storyboards (Part 1)

Soumya Mahunt
ITNEXT

--

There is huge debate among iOS developers regarding different approaches of building application user interface, the approaches being categorized as: Interface Builder (Storyboards/XIBs/NIBs), Code (Custom/SwiftUI) with each having their pros and cons that this article won’t go deep into. With the advent of SwiftUI building UI with code has become lot easier while allowing a lot more reusability. I read lots of posts comparing all these approaches, and none mentioned this particular advantage of building UI with Interface Builder due to the clear separation of UI from logic.

Typically when adding dynamic UI that needs to be consistent across app versions and can be updated without updating the app itself, UI is built with web frameworks while displayed using web-view. Similarly, while building third party framework or SDKs i.e. payments SDK, consumers can customize the UI without having access to data or state. In this article, we will see this use case being fulfilled by using completely native UI stack with the help of Interface Builder.

Interface Builder mechanics

Before diving into the use case and the advantage of Interface Builder, let’s first understand how IB works. The visual diagram built with IB is stored in XML format in xib or storyboard files. In fact, XIB stands for XML Interface Builder which is the oldest file format for IB. During build phase, the xib and storyboard files are compiled into nib and storyboardc by ibtool and copied inside the app directory or a specified bundle.

These files can then be loaded into memory by using UINib and UIStoryboard initializer or UIViewController initializer for view controllers defined in a XIB, and subsequently view objects can be created. Due to this separation of UI in a separate file, building UI with code performs better as no file load is needed. But this kind of separation has the following benefits:

  • Smaller update package when only xibs or storyboards are modified in newer version, i.e. only the nib or storyboardc files are updated.
  • Sharing same view object code across multiple nibs, i.e. using the same UITableViewCell sub-class for multiple cell nibs.
  • Independently update UI of the app without modifying underlying control logic.

While updating UI of the app dynamically without pushing app updates can be done in building UI with code approaches as well, but that requires establishing custom serialization and deserialization framework that takes some data from remote server to build dynamic UI. But with IB the unarchiving part is already provided by iOS, we just have to take advantage of it after implementing fetching data from remote server. In the subsequent part, we will implement a POC showcasing this use case in action.

Providing dynamic UI from server

To test the length of loosely coupling of UI built with IB and controlling logic, we will not add the nib files in the distributed app. Rather we will build a sample server that provides all the nib files. We will use vapor to build our server in Swift, that provides requested nib files to our iOS client.

Since our xib files aren’t included in an iOS product, they won’t be compiled to nib automatically by Xcode. But we can write a build tool Swift package plugin that compiles these files to nib by using ibtool as part of our server build process:

Now that in the nib files are generated as part of our build process and copied to server app’s bundle, we can provide these file to our iOS clients depending on the file name specified in the request made by client:

Building client with dynamic UI

With the changes our server is now ready to provide nib files to our iOS client, now, we just have to implement downloading these files and loading into memory to unarchive UI objects. But just downloading the file isn’t enough as to load file in the memory we need to specify the bundle that provides the file. If we look at any bundle, it is just a directory ending in .bundle. For our use case, we can just create such directory and store our IB files:

Now, with that out of the way, we can implement downloading the nib files and storing inside our newly created bundle directory. We can pass the file name and bundle instance to controller initializer, and display the dynamic page in our app.

We can use the same approach to register and display dynamic table view cells in our app as well. For tableview cells, we have to take extra care to not invoke the cell display logic in case the nib file isn’t downloaded already, doing so will prevent runtime exceptions.

The moment of truth

Now we can modify our controller and cell UI by modifying their corresponding xib file and rerunning our server to publish the latest changes. We can refresh our implemented dynamic page (by trying to land on the page again) to see our latest UI changes without having to redeploy or restart our iOS client app.

Dynamic UI demo with page color change and tableview cell text position change

One caveat with this is, as long as all the IBOutlets and IBActions are intact in the xib or storyboard file, the controller logic will work without any impact. Otherwise there is possibility of runtime exceptions with this approach. Additionally, in controller logic some IBOutlets can be declared optional to allow removal of such elements without causing exceptions in client iOS app.

Conclusion

Although the UI change shown here is only for xib file and pretty simple, this can be extended to storyboard file and complete UI revamp can be done by keeping IBOutlets and IBActions with the controller logic. Additionally, caching can be introduced for better page load performance and versioning of dynamic nibs can be implemented to link with controller logic present with multiple client app versions, preventing exceptions with missing IBOutlets and IBActions.

Let me know if you are using this approach in your app or are planning to build something like this. Also, if you are using any different approach for such use cases, let me know what drawback you foresee with this approach. The complete implementation can be found at:

Following are the resources used for this article, you can delve deeper into these for more customized use cases:

In the next part of this series, we will discuss making controller logic dynamic as well allowing to circumvent IBOutlets and IBActions limitations, allowing us to onboard completely new user journeys without any app update.

--

--

Writer for

Senior Software Engineer at MoEngage | System architecture enthusiast | Ex Tataneu