Developing UI at scale can be challenging.
At Schibsted, our large, distributed team of iOS developers collaborates on a single codebase. Our modular UI is shared by several apps, each of which applies a unique theme. Our requirements are effectively the worst case scenario for using Storyboards and Interface Builder.
Our solution to these requirements is Layout, a new collaboration-friendly UI system for iOS.
The Challenge
We needed a system that:
- Makes it easy for multiple developers to collaborate on the same screens and components, and to review and merge changes using standard tools.
- Allows for faster UI development than recompiling the whole app after every change.
- Supports runtime theming and customization.
- Is modular and composable.
- Is simple and familiar for existing iOS developers.
- Can deliver over-the-air updates for bug fixes or A/B testing.
So what’s wrong with the tools that Apple provides with the iOS SDK?
The Trouble with Interface Builder
When Apple first released the iOS SDK in 2008, they included Interface Builder, a WYSIWYG drag-and-drop tool originally developed for laying out desktop application windows, now adapted for the iPhone touchscreen.
Right from the outset, the iOS developer community was divided on whether this was the right approach, and many eschewed Interface Builder in favor of creating views programmatically.
Interface Builder has come a long way since it was first introduced, but the reasons for not using it remain largely unchanged:
- Nibs and Storyboards (the serialized UI files exported by Interface Builder) only support a subset of UIKit features. Any non-trivial screen will require parts of the layout or control wiring to be done in code, resulting in an awkward division of logic between the Nib/Storyboard file and source code, making it harder for developers to find where a change needs to be made.
- The XML format for Nib files (xib) is chock-full of unique machine-generated identifiers, making it impossible to effectively edit, review or merge changes. They can only safely be worked on by one developer at a time, even when using Git.
- Nibs cannot easily be subclassed or composed, and cannot reference code constants for fonts, colors, etc. All properties set inside Nibs and Storyboards are effectively “magic” values that cannot be assigned meaningful names, and can only be reused by copying and pasting them between screens that share common elements.
- Changing the appearance of views at runtime (e.g. for night mode, or to support multiple themes) means creating code bindings or subclasses for each and every on-screen view, and setting the properties programmatically. Different themes can’t be previewed at build time, eliminating most of the benefit of Interface-Builder’s WYSIWYG preview.
- The mechanism behind Interface Builder encourages use of implicitly-unwrapped optionals and string-based name/type binding that will crash at runtime if mistyped. This was onerous when using Objective-C, but is especially unpleasant when programming in Swift, which favors static typing and compile-time error checking.

Apple’s Interface Builder
While these problems have remained unsolved since its inception, Interface Builder has acquired new ones:
- When Nibs gave way to Storyboards, Apple encouraged developers to begin using IB for creating not just individual screens, but segues between screens as well, grouping multiple screens in a single Storyboard. This exacerbates the problem of file conflicts, as now developers cannot safely make edits to any screen in the app (or whatever subset of the app is contained within one Storyboard) without coordinating to avoid conflicts.
- Interface Builder was designed before the introduction of AutoLayout, when the layout system used a more traditional “springs and struts” model called Autoresizing. IB encourages you to place views with drag and drop, but AutoLayout constraints completely replace that model, making it redundant. The relationship between the views shown on screen and the constraints that position them there is confusing and brittle, and making significant changes to layout usually requires removing all constraints and starting again.
- IBDesignable, the new system introduced to solve the problem with previewing custom views, is flaky and doesn’t work well with code that is split across multiple modules, so a screen containing custom components often appears as a patchwork of empty squares in Interface Builder, rather than an accurate preview. It also puts the burden on developers to add extra boilerplate code to support design mode.
In the face of these problems, why not simply ignore Interface Builder and create views in code?
The Trouble with Hand-Coding UI
The most common alternative to using Interface Builder for UI is to create all your views programmatically. But this approach has its own problems:
- The more code you write, the more bugs (or potential for bugs) you introduce into your program. The files generated by Interface Builder replace human-generated code with machine-generated data, and as such reduce the space for bugs to hide.
- AutoLayout, Apple’s technology for specifying the size and positions of views on screen, is extremely long-winded to use in code. Despite the problems mentioned earlier, Interface Builder’s GUI approach makes it somewhat less cumbersome.
- Being able to drag and drop components and see the result immediately in a (mostly) WYSIWYG interface is a huge productivity gain over having to stop, edit, compile and run every time you want to see the result of a change.
- Apple expects developers to build their apps using Storyboards. Example code uses it. Default project templates are automatically set up to use it. Some features, such as localized app startup screens, are essentially required to use it.
This last point is perhaps the most important: Ultimately, any deviation from Apple’s status quo carries a cost.
The Trouble with 3rd Party Solutions
There are many existing 3rd party frameworks that address some of the issues, but none that meet all of our requirements:
- AutoLayout wrappers such as Masonry help to speed up development when creating views in code, but they don’t avoid the fundamental problem of having to recompile after every change – a problem exacerbated by Swift’s relatively slow compile times.
- Alternative development toolchains like React Native offer live reloading without recompiling, but at the cost of porting your app to JavaScript and React. Our iOS developers are happy with the power and flexibility of Apple’s UIKit and Foundation frameworks, and we already have a large codebase written in Swift, so switching to JavaScript would be a hard sell. It would also be a hard decision to reverse if it didn’t work out.
Over the years we have seen many such 3rd party development tools come and go, but the majority fail to gain significant traction because the pros so rarely outweigh the cons.
By adopting a nonstandard tool you run the risk that it will be slow to support new iOS features, or that its developers will stop maintaining it altogether. Even if it has a strong development community behind it, that community is a tiny fraction of the of the total iOS developer base – that impacts your ability to find developers who can work on your product, or to find solutions to bugs on Stack Overflow, and so on.
In light of that, it seems like creating our own UI framework may seem like an odd decision, so why build our own tool? In short, because the problems are real, and the potential benefits of solving them are worth the risk.
But if we are going to pay the cost of adopting a nonstandard tool, it better be something we are comfortable using, that meets all of our requirements, which we can maintain ourselves, and which is easy to migrate away from again if our requirements change.
That’s where Layout comes in.
Layout
Layout (available here from Schibsted’s github page) is a drop-in replacement for Nibs and (to some extent) Storyboards, based on human-readable XML files that can easily be edited and merged.
In place of hard-coded constraints, Layout offers dynamic, parametric layouts via the use of runtime-evaluated expressions (more on these later).
In place of WYSIWYG editing, Layout offers live reloading, so you can make tweaks and bug fixes to your real UI, in-situ, without recompiling the app.
In place of procedural, stateful view logic, Layout offers declarative data binding.
Like Nibs and Storyboards, Layout uses reflection to automatically recognize and bind components at runtime, so there is no need to create plug-ins for your existing view components – they will mostly just work without changes.
Unlike Nibs and Storyboards, Layout’s runtime bindings are fully type-checked and crash-safe if used correctly. Type or naming errors are detected statically and reported at the first opportunity, not deferred until a button is pressed or an outlet is accessed.
Layout is written in 100% pure Swift code. There is no reliance on JavaScript or any other resource-hungry scripting language.
Most importantly, Layout is not a replacement for UIKit. It is not a cross-platform abstraction, and it does not provide its own UI components – everything that Layout puts on the screen is either a standard UIKit view, or an ordinary UIView subclass that you have built yourself.
It is easy to add Layout to an existing project, easy to use existing components, and easy to remove it again if it’s not the right fit. You can use a handful of Layout-driven screens or components in an otherwise standard app, and it need not affect your app architecture in the slightest.
Expressions
Layout’s XML files describe view properties in terms of runtime expressions, powered by the Expression library. Expressions are simple, pure functions that allow dynamic values to be specified in a declarative fashion.
In recent years, the development community has come to the realization that traditional, procedural code that relies on mutable state is a difficult way to manage complex logic such as user interfaces. Frameworks such as Facebook’s React have demonstrated a simpler, more maintainable approach to UI based on the composition of pure functions that take state as an input and produce a view hierarchy as the output.
Apple’s own AutoLayout framework is also based on this idea, replacing complex procedural code with dynamic constraints that can describe flexible layouts mathematically instead of programmatically.
So why doesn’t Layout just use AutoLayout for view positioning? It would be relatively simple to specify AutoLayout constraints in XML and rely on that for positioning views, so why reinvent the wheel?
Despite its name, the scope of Layout goes beyond merely laying out views – it can be used to configure essentially any property of a view, not just its size and position. And because these properties are specified in terms of expressions rather than static values, the ability to describe flexible layouts comes essentially for free.
So Layout doesn’t use AutoLayout because it would be redundant – you can replicate all the features of AutoLayout using expressions, as well as more complex behaviors that cannot easily be described in terms of AutoLayout constraints.
With that said, Layout does interoperate nicely with AutoLayout-based views. If a view has internal AutoLayout constraints, Layout will use those to determine its size.
Here is an example. This is the native Swift code in the view controller which loads the XML template and passes in some constants to use:
1 2 3 4 5 6 7 8 9 10 11 12 |
import UIKit import Layout class ExampleViewController: LayoutViewController { override func viewDidLoad() { super.viewDidLoad() loadLayout(named: "Example.xml", constants: [ "image": UIImage(named: "Rocket")!, "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit." ]) } } |
The XML describes a UIView containing a UIImageView and a UILabel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<UIView backgroundColor="#aaa" height="auto + 20" width="100%"> <UIImageView backgroundColor="#fff" contentMode="scaleAspectFit" image="{image}" left="10" top="50% - height/2" width="max(intrinsicContentSize.width, intrinsicContentSize.height)" height="width" /> <UILabel font="120% bold" left="previous.right + 10" numberOfLines="0" right="100% - 10" text="{text}" textColor="#fff" top="50% - height/2" /> </UIView> |
All the dimensions here are flexible: The image and label are sized to fit their content; the container view is set to 100% of the width of its container, and its height is set to auto + 20
, which means it will fit the height of the image view or label (whichever is larger) plus a 10-point margin on either side.
The image view’s width is determined to be whichever is the larger of its intrinsic width or height (determined by the source image), and the height is set to match the width, so that it is always square. The label is positioned 10 points to the right of the image view, and the image view and label are both vertically centered in their container.
The expressions used to calculate the value for each layout property should look familiar to any iOS developer – they are very much like the layout code you might have written inside a layoutSubviews
method in the days before AutoLayout. But these expressions are not compiled Swift code, and they can be tweaked and debugged without rebuilding the app.
Below, you can see the resultant view as it would appear in portrait and landscape on an iPhone:
Live Reloading
Developing a pixel-perfect layout involves a lot of iteration, and developer productivity depends on making that process as fast and painless as possible. Layout’s solution to this is live reloading, a mechanism which allows Layout to reload XML files on-the-fly without recompiling the app.
Live reloading works by scanning your project directory for layout XML files and matching them up to the versions bundled in the built application. Layout then loads these files preferentially over the bundled file. If Layout cannot locate the file it will display an error, and if it finds multiple candidate files, you will be asked to choose which one it should use.
Layouts can be reloaded at any time using the Cmd-R keyboard shortcut when the simulator has focus.
Unlike React Native, Layout’s live reloading system is serverless, and does not rely on any background processes. The live reloading code is encapsulated inside your app, and only included in apps built for the simulator, as a real iOS device doesn’t have access to your Mac’s file system.
The Red Box
Almost any mistake you make in a Storyboard will result in a crash inside UIKit, leaving you hunting through the console log trying to work out where you went wrong. Layout’s live reloading feature wouldn’t be much use if every typo resulted in a crash, so instead we have the Red Box.
The Red Box (a concept borrowed from React Native) is a fullscreen error dialog that appears whenever you make a mistake in your XML. Whether it’s a syntax error, a misnamed property, or a circular reference in one of your expressions, the Red Box will tell you exactly where you went wrong, and a tap will dismiss it again once the problem has been corrected.
Layout’s error handling is enabled in production apps too, so you can intercept and log production errors, and then either crash or fail gracefully at your own discretion.
Over-The-Air Updates
While Nibs or Storyboards can be loaded from a remote URL, there is no official support for this in UIKit, and doing so is very unsafe, as a mismatch between app and Storyboard versions may result in an untrappable crash.
Layout supports asynchronous loading of remote XML files and has the basic infrastructure in place for over-the-air UI updates. Errors can be trapped and handled gracefully, so that the app can roll back to a safe state in the event of a bad update.
Convenience
There are a number of rough edges in iOS development, and Layout takes the opportunity to simplify those wherever possible. For example:
- Fonts: Programmatically specifying a particular font at a given weight and style can be tricky; you need to know the exact font name and/or mess around with nested dictionaries of stringly-typed constants to specify the required attributes. Layout lets you specify fonts as a single space-delimited, CSS-style string that it parses to find the closest match. Font sizes can be specified in fixed or relative units, and Layout provides seamless support for iOS’s dynamic text resizing feature, even for custom fonts, which normally require you to calculate point sizes manually.
- Colors: Colors are almost universally specified using a 6-digit hex format or a triplet of RGB values in the range 0-255, but iOS requires you to specify RGB values in the range 0-1. This makes sense mathematically, but it’s a pain to do this conversion manually. Layout lets you use 3-, 4-, 6- or 8-digit hex color literals directly in your XML, along with CSS-style rgb() and rgba() functions.
- Rich Text: Creating attributed strings in code is hard work, and to date iOS still doesn’t provide any way to put styled text in a localized strings file. Layout lets you drop basic HTML straight into your XML file for doing simple styling like bold or italics, and if you use HTML in your strings file, those strings will be automatically converted to attributed strings when used with Layout components.
- Table cells: Storyboards provide a convenient way to create cell and header templates directly inside your table. But if you later decide that you need to share those cells between more than one screen, that’s an awkward refactor. Layout lets you specify XML cell templates either inline within the table or in their own file, using exactly the same syntax, making it painless to refactor later.
- Localization: Using localized strings inside Interface Builder is awkward as you have limited control over key names, there is no automatic way to add new strings after the initial extraction, and no warning if you forget or mistype a key. Layout lets you reference strings from your
Localizable.strings
file directly in your XML, and will display an error if any string is missing. It also applies the same live loading feature to strings as it does for XML, so you can add, modify, or remove strings and see the results without recompiling the app.
Roadmap
Though it has only been in development for a few months, Layout has already been integrated into several Schibsted apps, and is running in production on the App Store.
In the next few months, we’ll be focussing on:
- Supporting more special-case iOS components, properties and types
- Even more robust error handling, and improved unit and integration test coverage
- Better tooling, such as static analysis, linting and refactoring for template files
- Improving the examples and documentation
We’re really excited about Layout’s potential, which is why we’ve decided to open it up to the wider iOS community. Your feedback will help it to grow faster, and to expand it support new use cases beyond our requirements at Schibsted.
Links
The Layout framework on Github:
https://github.com/schibsted/layout
Expression – the library that powers Layout’s runtime expressions:
Great tool! One question: What happens if I want to animate views, does layout have support for UI animations?
Yes, you can use ordinary UIView animations in much the same way as you would when animating AutoLayout constraints. Basically you just set the LayoutNode’s state inside an animation block and any changes will be animated automatically.
Check out the Pages tab in the SampleApp for an example – the Toggle button animates the layout between two states. The other examples also animate when you rotate from portrait to landscape.
Mind. Blown.
I’m curious, and have only made a cursory scan–
Does your GitHub repository for this project link back to this post? I only wonder if there’s useful information (and maybe just the presentation here is useful in a different way) here that isn’t there, and visa-versa.
It doesn’t but that’s a good idea – I’ll add a link back to the article.
Very interesting project and thanks for the great introduction! I wonder if you have any plans to implement this for Android too? I am sure the Android XML might end up quite a bit different to the IOS XML, due to its very different controls and control properties. But having a similar dynamic native UI engine on both OS’s would open up some interesting possibilities.
I agree it would be interesting to be able to use the same approach on both platforms (and maybe Web too), but as you say, there would have to be significant differences in order to remain true to each platform rather than trying to share templates at the cost of losing “nativeness”.
Also, my understanding is that Android’s XML-based UI already solves many of the problems I’m trying to tackle with Layout (with the possible exception of live-reloading), so it’s not as big of a win there.
I won’t rule out some kind of x-platform support in the future, but for now my focus is on iOS exclusively.
This is awesome! Yes, Android layouts are declarative and source-control friendly. It actually looks very similar to the XML you have defined. 🙂 Maybe you can influence Apple to adopt this framework, instead of their arcane, Xcode-generated storyboard files.
This looks very promising and useful! Just curious, what about the possibility of supporting JSON in addition to XML for the layout files?
{
"UIView": {
"backgroundColor": "#aaa",
"height": "auto + 20",
"width": "100%",
"subviews": [{
"UIImageView": {
"backgroundColor": "#fff",
"contentMode": "scaleAspectFit",
"image": "{image}",
"left": 10,
"top": "50% - height/2",
"width": "max(intrinsicContentSize.width, intrinsicContentSize.height)",
"height": "width"
},
"UILabel": {
"font": "120% bold",
"left": "previous.right + 10",
"numberOfLines": 0,
"right": "100% - 10",
"text": "{text}",
"textColor": "#fff",
"top": "50% - height/2"
}
}]
}
}
Unfortunately that format wouldn’t work unless every subview was a unique type. The actual format would have to be something more like:
[
{
"class": "UIView",
"backgroundColor": "#aaa",
"subviews": [
{
"class": "UILabel",
"text": "#aaa",
},
{
"class": "UILabel",
"text": "#aaa",
}
]
}
]
It wouldn’t be difficult to add support for something like this, but JSON is not particularly well-suited to representing hierarchical documents – it doesn’t distinguish syntactically between types, attributes and children, it requires quotes around each attribute, it doesn’t support comments, and it doesn’t support inline HTML for rich text markup.
XML is certainly not perfect, but on balance it’s the best of the available formats that iOS has built-in support for. If I move on from XML it will probably be to something like YAML, or perhaps a bespoke format, although both of those have their own disadvantages.
Great tool 🙂
I haven’t test it yet, but please develop your great tool for RTL languages too.
It took a while, but you’ll be pleased to know that Layout now supports RTL!
Yes Yes and Yes! Fantastic post 🙂
First off I am so glad to see that turning down on Storyboards is gaining popularity! People usually think you’re a freak for not using IB but this solves so many problems and I’d never go back to Storyboards.
Super happy to see people taking the declarative view road as well. React native helped a lot but ain’t a big fan of having a JS engine running to run the app. As a community we need to realize that we can have a lot of what React Native offers but in pure Swift.
This post really resonated because we’ve faced the same issues at Yummypets and first open sourced https://github.com/freshOS/Stevia for clearer auto layout in code.
More recently we worked on https://github.com/freshOS/Komponents which is very similar to what you guys worked on: Pure Swift views with live reload ! (you should check it out when you have some time)
So excited to see what the future brings 🙂
Thanks again for the great post and splendid work !
This is great work and almost exactly the kind of thing I wished someone would have written (and years ago at that).
A few quick questions/suggestions.
First: does this support named colors (e.g. for colors defined in an asset catalog) yet? If not, presumably you will eventually want some way to specify colors for use with wide-gamut displays.
Next: does your support for custom fonts that auto-scale along with dynamic type use the built-in apis (as of ios 11)? If not this seems like an easy way to wind up with scaling that feels slightly “off”.
Thirdly: to what extent does this support variant layouts for use with specific size classes? This seems like it’ll keep getting more important as Apple keeps pushing harder on iPad multitasking.
Fourthly: what’s the story for things like setting delegates, setting actions, and so on? I’m sure you thought of this one but the sales pitch would be better if it included an example!
Fifthly: does this allow first/last baseline calculations in the layout expressions? It’s important to have that for precise layout with variably-sized fonts!
Finally: I would suggest you take a close look at the design of the TVML stack, in particular the API for vending custom elements. In particular it has an approach for both view-recycling and custom elements that seems like it could be grafted onto this framework to great effect (although it could benefit from some design tweaks)!
Hi, sorry for the delayed reply.
> First: does this support named colors (e.g. for colors defined in an asset catalog) yet? If not, presumably you will eventually want some way to specify colors for use with wide-gamut displays.
It does! You can reference a named color asset directly in any color expression.
> Next: does your support for custom fonts that auto-scale along with dynamic type use the built-in apis (as of ios 11)? If not this seems like an easy way to wind up with scaling that feels slightly “off”.
Yes, you can specify system-defined UIFontTextStyle names like
title1
orbody
in font expressions to use the standard font sizes, and you can specify font sizes using percentages to get generate intermediate sizes that will scale automatically.> Thirdly: to what extent does this support variant layouts for use with specific size classes? This seems like it’ll keep getting more important as Apple keeps pushing harder on iPad multitasking.
Great question! There’s no specific support for size classes currently, but you could pass the size class in as a constant to be used in your layout expressions, or you could have branching logic in your expressions that takes the view width or height into account to specify different values if the screen is above or below a certain size.
> Fourthly: what’s the story for things like setting delegates, setting actions, and so on? I’m sure you thought of this one but the sales pitch would be better if it included an example!
Delegates are bound automatically if the view controller implements the appropriate protocol. Actions can be bound explicitly by specifying the name of a method on your view controller.
> Fifthly: does this allow first/last baseline calculations in the layout expressions? It’s important to have that for precise layout with variably-sized fonts!
There’s no support for baseLine alignment at the moment, but this will be added in future.
> Finally: I would suggest you take a close look at the design of the TVML stack, in particular the API for vending custom elements. In particular it has an approach for both view-recycling and custom elements that seems like it could be grafted onto this framework to great effect (although it could benefit from some design tweaks)!
Thanks for the suggestion – I will certainly do that. If you have any more questions about the framework, check out the README file included in the repository, as it is fairly extensive. If you find anything that isn’t covered well enough, please open an issue on Github. https://github.com/schibsted/layout
This made me return to iOS Development!
What are the pros and cons of Layout when compared with MarkupKit https://github.com/gk-brown/MarkupKit?
I hadn’t come across MarkupKit before. From the description it seems remarkably similar to Layout.
One key difference appears to be in the approach to live reloading. MarkupKit uses IBDesignable to preview layouts in Interface Builder, whereas Layout can live reload changes at runtime in the Simulator itself.
They also use different layout systems, but I’ve not had time to evaluate the pros or cons of MarkupKit’s approach.