How to Create a Container Framework

We've built and open sourced a container framework!

A few months ago, a client requested that we implement a data-driven layout system in both their iOS and Android apps. This system had to be able to, given a specifically formatted JSON response from the remote server, decompose it into ‘components’ that should be configured with the data provided. The client’s app then had to be able to lay these components out on the screen and stack them. The server thus needed to be able to change the layout and decide what components should be rendered by re-ordering and sending the elements in the response (as opposed to having to update the design and do a new release). One important requirement was that different teams should be able to use the system and they should have the ability to reuse existing pieces of code so that it could be used across different products. In this post, we’ll be looking at how we met client requirements using our container framework. 

 

Organising the container framework

Being mindful of client requirements, we roughly broke the development process into the following tasks:

 

  • Decomposition of the server response into domain objects, which then had to be mapped to the component to be rendered.

 

  • Implementation of a rendering system capable of managing this dynamic layout.

 

  • Implementation of individual components to be rendered by the client app.

 

The decomposition of the server response and the subsequent mapping was pretty much straightforward.

The implementation of the individual components could be accomplished without depending on the rendering system due to some of the decisions made. 

The real challenge was the rendering engine, which lead to the development and the subsequent creation of the ContainerCollection framework.

 

What is the ContainerCollection framework?

In short, the ContainerCollection is a framework providing a rendering engine which enables the developer to easily arrange components in the screen, whilst still managing the recycling and memory management underneath. 

We built it on top of UIKit so that that we could use all UIKit native components to render our custom views.

 

Phase 1: Analyse requirements and outline specifications

With the client requirements for the data-driven layout system in mind, we started to outline the specifications for the rendering engine.

We ended up with the following list of framework specifications:

 

  • It must define rectangles on the screen to embed components on.

 

  • It must provide data to the components.

 

  • It must avoid enforcing any design pattern or architecture, leaving this responsibility to the development team using the framework.

 

  • Its usage must be as easy, intuitive and familiar as possible for any iOS developer.

 

  • Implementation should be as simple as possible – relying on native UIKit and Foundation elements where possible. We didn’t want to waste time reinventing the wheel!

 

You may have noticed that, up until this point, we keep mentioning ‘components’ but we have yet to define them. 

That’s because until this stage in the process we hadn’t considered which element to use as components but we had clearly outlined what we wanted components to do. 

 

Components:

 

  • must be able to configure themselves using only the data provided by the container.

 

  • must be recycled to avoid creating too many instances and, consequently, a massive memory leak.

 

  • should be independent of the container and capable of working as standalone elements in an iOS app.

 

  • may have one of the containers nested, may also have components nested (including itself) and may even consist of sole components either stacked or in a complicated layout.

 

Phase 2: Agreeing on a starting point

Now that we have outlined the specifications for our internal rendering framework it’s time to start coding!

But, before that, we must decide if we wish to implement everything from scratch, relying only on the most basic components of UIKit, or alternatively customize a native and already fully functional UIKit object as the renderer system.

Taking the previous specs into consideration, along with the client’s requirements, it was clear that we required a system capable of stacking and recycling components. We had a tight schedule to work to, and since UIKit already provided two different implementations of such a system, namely UITableView and UICollectionView, we decided to use those as a base for our rendering system. We refer to those as containers.

We had considered other UIKit elements for our base, even considering the possibility of implementing a recycling system to UIStackView. Ultimately, however, we discarded this option due to time constraints. 

With regards to the components themselves, the aforementioned specifications left us with just two options. We could use either UIView or UIViewController classes as a base for our components. We decided to use UIViewController subclasses, despite the fact that they’re more complex than UIView classes,  because the flexibility afforded to us with the controllers was worth it. 

View controllers are the base element for all iOS apps and well known to iOS developers, thus preventing the need to write detailed documentation all about how to build a component. They would just need to build a view controller as usual!

The main reason for using UIViewController, however, is that it does not enforce any architecture or design pattern. It’s one of the most basic elements in any iOS app! This enables multiple teams to build components simultaneously without even agreeing to a common approach. Every team’s goal is to create a UIViewController and they can achieve it in any way! 

In fact, you don’t even need to take a look at the framework before starting to implement components!

It may surprise you that we didn’t consider using cell subclasses, like UITableViewCell or UICollectionViewCell, especially as we had already decided to use both UITableView and UICollectionView as the base for our rendering system. We did consider them but that idea was quickly discarded as it would mean that the component would have been built for the particular container – thus preventing reusability of code and the ability to use them as standalone components.

When considering our components as UIViewController subclasses, we were still required to implement some kind of cells but these would only act as a wrapper for the component in order to embed them. They wouldn’t run as the main component itself.

 

Phase 3: Code, code, code!

We’ve got the specifications and we’ve decided upon how we are going to implement the framework so it must be time to code! Not exactly…

Coding, in reality, involves a lot of meetings and discussions to determine the best approach. Furthermore, a considerable amount of refactoring took place during the implementation stage. Despite using a common code styling guide, we all had our own opinions on the specifics of the implementation. 

Should this be a protocol or a base abstract class? Should we provide a default implementation for this protocol for most use cases or let every client implement their own? Shall we use a standard delegate pattern or rely on a modern functional programming approach? 

All of these questions and more were raised and addressed during the implementation, requiring several refactors as mentioned. 

I won’t deep dive into the specifics of how we implemented the framework, since you can take a look at the code on GitHub,  and it was pretty atypical of the typical software development process. 

 

Container frameworks: the ‘Dynamic Height’ issue

During our first approach, we decided to use UITableView or UICollectionView as a container as we wanted to give our cells the ability to adjust their sizes based on their content. 

For UITableView, allowing a cell to size itself via Auto Layout is very straightforward. You more or less just set the table view’s row height to UITableViewAutomaticDimension and make sure you’re using the right constraints for your cell subviews. 

For UICollectionView, however, the solution is a little less obvious. You have to set the collection view flow layout estimatedItemSize property to a non zero CGSize and make sure the subviews constraints are well defined in relation to the contentView of your cell.

How do we manage an asynchronous update of the layout when scrolling through a stack of multiple view controllers? That’s something that our framework is currently not able to manage. 

 


Did a dynamic cell height really make sense for our container?

 

The answer was no because a stack of components needs a fixed height to be more efficient in terms of performance.

The container framework was designed to be a fast layout rendering engine. That’s why if you need to add infinite elements inside one of your components you can always implement it outside of the stack.

The stack has to be dynamic in the sense that you can always add or replace or modify the order of the components as per your preferences. This couldn’t be coupled to a specific behaviour such as the dynamic cell height. 

 

how to build container frameworks

 

Phase 4: Example of a container framework app

We knew, since day one, that we wanted to open source the container framework. This involved creating some kind of example app to both illustrate the purpose of the framework and give an example of how to integrate it with an existing app.

Since the framework had originally been developed to be part of a client requirement, we couldn’t just use the components which had already been developed due to legal restrictions. We, therefore, had to create a new example app from scratch.

Lee had a wonderful/crazy suggestion: ‘Let’s go mental and stack thousands of heavy-consuming components to test it thoroughly and prove that it really manages memory consumption properly’. 

And so, we did. 

If you take a look at the example app included in the repository, it only has a few components implemented. Among those, however, you’ll find a map, a video player and a scene view (rendering simple geometric 3D rotating objects!).

As you’ll see if you run the app, you can opt to use a standalone component or stack them. If you want to do the latter, you’ll be asked for the number of components to stack (any number between 1-10000 is valid). As explained above, all of the components are just subclasses of the UIViewController and, therefore, work perfectly by themselves. When stacked the components are exactly the same but the data displayed is given by the container. 

In addition, the components with scrolling or dynamic heights (such as the map) do disable the scrolling and use a fixed height. Furthermore if you choose to stack 10000 components, you’ll notice that scrolling components are quite smooth, with the exception being when a memory warning is triggered. This is when the framework cleans unused memory and freezes for a second. 

The performance was great even when tested on an iPod Touch!

 

Phase 5: Documentation

The final step before release is also one of the most important and tedious steps: documenting the framework. This is not just about creating the README.md file with the installation instructions. It also concerns adding comments to the code, a step-by-step guide to implement a new component and more.

The issue with writing documentation is that you must put yourself in the place of a developer that has never used this framework before. The developer may even be a newcomer to the iOS community. Documentation can thus be a little difficult to write.

At this point, we’d been working on the framework for several months in a row. This implies that there were a few (read: a lot) of things that we took for granted, that someone taking a look at it for the first time wouldn’t. With this in mind, we’ve tried to be extremely thorough and detailed with our framework docs. 

 

Phase 6: Release

And, finally, the moment of truth: releasing the framework! 

As always, it sounds simpler than it actually is. Especially since the release included:

  • Creating a new public repository in GitHub and uploading the code (as we had been working on a private repository).
  • Setup permissions and protected branches in GitHub.
  • Creating the release in GitHub.
  • Publishing the repository in the main CocoaPods repository.

 

It’s also worth mentioning that the ContainerCollection is an Open Source project, so we included an Open Source license and we followed the Open Source guidelines.

So, after working through all of the aforementioned steps, the ContainerCollection framework is finally here.

Take a look!

 

Further container framework development

Unfortunately, we didn’t have time to implement everything we wanted. 

Here are some ideas that we have for the near future:

 

 

  • Saving and restoring UI state

 

Some components require specific UI states to be preserved between reuses when the same data model is rendered. For example, if the items in a component are selectable you might want to retain the selected item so that state isn’t lost when the component is reused.

 

 

  • Integrate a CI solution to deploy the framework. 

 

As you may have noticed, there is a Fastlane folder in the repository. We want to improve the framework in time and add automation (using either Travis CI or another CI solution) for the deployment process.

 

 

  • Add a tvOS example app.

 

The framework does support tvOS since it was a requirement that we had from the beginning, so we want to create an example app to show how easily we can stack rails and other views using the framework. This is currently in progress.

 

 

  • Improve tests.

 

Since this is essentially a framework to help create complicated layouts, most tests are actually UI tests which verify if the components are loaded correctly. We could probably add more, however, and also improve upon the existing ones.

 

In conclusion…

The development of this framework has been a real challenge!

Implementing UI frameworks is usually a difficult task for most developers since native UI components in iOS tend to have different behaviours depending on the iOS version that your device is running.

In addition, debugging UI bugs is usually quite tedious since they’re usually missed by the compiler, you must re-compile them every single time and you also have to run an app to spot them.

The iOS team here at We Are Mobile First have all acknowledged that this has been an outstanding team effort. Everyone (and I really mean everyone) in the iOS team has contributed to the development of the framework – whether that’s during its conceptual design, coding, the documentation process or even writing this very blog post. We all worked together to pool our strengths, meaning we’ve been able to create a framework that is more astonishing than anything we could’ve created individually. 

On a personal note, although I usually don’t like performing UI tasks, I must admit that I’ve really enjoyed developing this framework. I have learned a great deal. My knowledge of the Swift programming language has definitely improved, as well as my knowledge of UIKit (the basic iOS UI framework) and how to reuse UI elements.

The result that I value the most, however, is what I’ve learned about my colleagues along the way. Our working relationships have improved thanks to this ‘side project’  that we’ve taken on. It’s been a real months-long team-building exercise!

If you’ve never embarked on a project like this before, don’t hesitate to give building your own framework a go!  I recommend working alongside your peers to make the experience of creating a reusable, independent framework all the more poignant. 

 

By Pere Daniel Prieto, Francesco Deliro and Christian Aranda.

 

____

Do you think you’ll attempt to create your own framework? Tweet us and we’ll be sure to retweet the responses!

We Are Mobile First is a digital product agency based in Barcelona helping to transform businesses in a mobile-first world. Follow us on Twitter, LinkedIn and Medium to be notified of our future posts and stay up-to-date with our company news. 

We share weekly content on everything from how to successfully lead a software development team to pair programming.