Building our Lego library on iOS
A year ago our fellow designers at BlaBlaCar introduced the notion of Atomic Design via their Lego metaphor. Their idea was powerful; every screen of their design could be decomposed into bricks of UI components that could be easily reused when new screens need to be designed: this allowed them to focus more on the experience rather than interfaces by building mockups in few seconds.
There was no reason not to apply the same principle to our development process and that’s what our iOS team did. After several iterations and nearly three months of hard work we had the first version of our Lego library in Swift capable of providing a complete set of highly reusable UIKit elements for the application.
This had two positive outcomes: faster development by having less boilerplate code when we need to create new custom views and improvement of pages uniformity.
So what were the key elements that made our library successful? And what should you be considering when building a UI framework of reusable components?
Let’s start with the Foundation: that’s the key element of the library to define the architecture of each Lego component.
Data, Style and Layout: the three pillars of our Lego library
We constructed each Lego around three data structures defined as generics:
- Data: to store all the values that are needed to feed the Lego with content.
- Style: to contain everything related to the style of the Lego such as font and color. It can also define the style of inner Legos.
- Layout: to abstract the Lego layout and the one for its children.
A full implementation of the Lego parent class can be found here.
We didn’t consider the Lego as one single component inheriting from UIView and responsible for all the styling and layout. Instead we favoured a separation of concerns when it comes to data feed, style and layout. The lego was then delegating this work to those three players and orchestrating them.
Let’s take a deeper look at how it worked on a concrete implementation of a Lego subclass: the HeroLego. This Lego helped us extend the navigation bar with elements such as text and an image of a profile:
As shown in the source code below, the implementation required a minimal set of functions to properly configure the Lego:
- initialiseSubviews: to create each subview and add it to the view hierarchy of the Lego.
- bindData: to feed each data element to the proper subview.
- applyStyle: to apply a specific style to each subview.
You may have noticed that we set up few rules concerning the data and the style:
- We allowed the Lego to have variable data during its lifecycle. When feeding new data or during the initialisation we triggered internally a set of calls via the udpateLego method that ensured the Lego intrinsic content size and layout were computed every time the data changed.
- We defined the style as immutable but with different variants since it couldn’t change. For example a primary button was not supposed to become a secondary button.
There are two schools of thought when it comes to layout on UIKit: Autolayout and Fixed Frame computation. We chose the latter to ensure we didn’t sacrifice performance for tableview display. To put things into perspective this approach was a bit of premature optimization and you probably want to instead consider a solution where both approaches can be supported.
The code related to frame computation was hard to read between teammates even though we added support in expressing frame computation with this library.
Now that we have settled on the Fixed Frame approach let’s have a look on how we expressed this in our code. The layout of our Lego is handled by the Layout struct we’ve just introduced in the code snippet above. We’ve made this struct conformed to the LegoLayout protocol to ensure it could provide intrinsic information size of the Lego after laying out its subviews. This required taking into account two associated types: the LegoDataType and the LegoStyleType.
While conforming to the LegoLayout protocol the Lego layout struct took care of providing a frame property for every Lego subviews. Here is an example of interaction with the layout struct within the Lego subclass.
Consider using Facebook Snapshot View unit tests for iOS when building a UI framework.
We always put a lot of effort into tests at BlaBlaCar to ensure the quality of our code base. We do it by unit testing every bit of code we put in production. Our Lego library was no exception and we currently have unit tested all the components of the library.
We took a slightly different approach though in comparison to our main application as we were dealing with a UI library: nearly of all our tests leverage Facebook Snapshot View SDK.
Facebook Snapshot View comes into two modes:
- a record mode in which you can configure
CALayerand use the
renderInContextmethod to take a snapshot of a reference image.
- a default mode that takes a snapshot against the reference image stored locally. Test in this mode will fail if two images don’t match.
This proved to be extremely handy when building and making the library evolve. It actually brought us two majors benefits:
- For any refactorization of our lego, UI unit tests served to ensure we didn’t alter the look and feel of the Lego component we worked on.
- For PR reviews we could easily track any UI changes that were introduced by the owner of the branch: issues with the margins, wrong typos or font sizes… you name it but at end they all could be easily spotted.
Consider using a sample application for validation of your components by your UX team.
During the initial phase of the project we regularly showcased different components implemented via a sample app to our product managers and designers. We saw an immediate value in doing so; it helped us spot edge cases which were not obvious during Sketch design and fell through the cracks.
The value of the sample app diminished over time as it came with a cost of maintenance. However it became very handy again as we evolved the look and feel of the Lego. This helped the designers to rely on this application to quickly spot inconsistencies without going to every screen (117 screens 😱 ) of the main application.
Across the year our Lego library has evolved with some components offering more traits and with the addition of new ones.
The major update we brought along the way was the support of various look and feel so we can easily switch fonts and colors across application if needed.
We achieved this goal by decorrelating the fonts and colors and via three actors:
- StyleGuideType protocol which abstracts the color scheme and font book. Every Style structure can conform to this protocol to directly have access to all supported styles and colors.
- A StyleGuideProvider which provides concrete implementation of our color palette and font book. This shouldn’t be part of the Lego but provided by the application or an external framework that’s only in charge of providing the theme.
- A StyleGuideManager which ensures bridging between the StyleGuideType and the StyleGuideProvider. During initialization of the Lego the StyleGuideManager is required to have its delegate setup so it can bridge the color palette and font book defined in the StyleGuideManager.
Alternatively here is the diagram of this architecture:
We hope this article gave you a good insight on what should be considered when building a UI library on iOS. The key to success for us was having a very simple architecture to define the foundation of every Lego without imposing too many constraints.
The Lego is only the beginning of the journey with our collaborative work with designers. This library will soon become part of a bigger framework which is to build a design system. Its purpose will be larger with the templatization of pages for singular purpose (e.g validation, confirmation, autocomplete pages etc…). We also plan to provide deeper support for animations and improvement on our layout engine which was not optimal.
Have you already architectured this kind of framework? Feel free to share your experience with us: we are convinced there are many possible approaches to this!
Component-driven UI frameworks:
Composable styling in Swift: https://medium.cobeisfresh.com/composable-type-safe-uiview-styling-with-swift-functions-8be417da947f