iOS UI development in Badoo
Hey! Being a developer in Badoo is not only interesting and fun, but also challenging — you have to produce highly maintainable code in a short period of time. Due to the amount of new features coming to our team on a weekly basis, we have to think of the architecture in the first place, otherwise it will be impossible to add new functionality to the app without breaking existing features later. Obviously this also applies to UI implementation whether it’s done via code, xib or using a mixed approach. In this post I’m going to describe some of the UI implementation techniques we use here in Badoo, which make UI development easier, more extensible and testable for our team.
Before we start…
As a starting point for this article I’ll be using a sample app written in Swift to demonstrate UI implementation techniques. The app shows a list of friends upon a button click. It also consists of the following three domains:
- Components — custom UI components, which means UI related code only;
- Demo app — demo view models and other entities which have only UI-related dependencies;
- Real app — view models and other entities which also may contain domain specific logic and dependencies.
Why such separation, you may ask? I’ll give you the answer in just a few moments. For now, take a quick look at the UI of the sample app:
This is a popup view with content on top of the full-screen view. Simple as it is.
The full source code of the example project is worth checking and is available on github.
Value observing utility
Before we dig deeper into the UI code, I want to present one utility class which I’m using in the sample app — Observable and its interface looks like this:
What it does, is simply notify all previously subscribed observers about the changes in the stored value, so it’s kind of an alternative to KVO observing or Reactive observables if you wish. Here is a usage example:
This code subscribes to the changes in self.viewModel.items property and when there’s a change, the observer closure executes some business logic like updating the view’s state and reloading collection view data with new items.
You will see more usages of this utility class later in this post, but now you are prepared!
Here we go: the techniques
So the fun starts here, in this section I’ll present four UI development techniques we use here in Badoo which are:
- Implement UI in the code;
- Use layout anchors for simplicity;
- Divide and conquer with components;
- UI and logic separation.
I have to say that the second one is not crucial, but it’s still good to know about the existence of this technique. So let’s dig in…
#1: Implement UI in the code
Here in Badoo, we implement most of the UI in the code. You may ask why we don’t use XIBs or Storyboards, and it’s a fair question. In short, the reasoning for this approach is the maintainability of the code in a medium-large team, and this includes:
- Clear changes in the code, and so clearer code reviews, which means there is no need to parse the XML of a Storyboard / XIB file looking for the changes made by your team mate;
- It’s much easier for Version Control Systems like GIT to work with the code instead of heavyweight XML files, especially during merge conflicts, also considering that XIB / Storyboard file content is being changed on every file save, even when the UI wasn’t changed at all (I heard this should have been fixed in Xcode 9, but I still get this problem from time to time);
- It could be hard to change and maintain some properties in Interface Builder (IB), like layer properties upon subview layout for example, and this may lead to a multiple sources of truth of a view’s state;
- Interface Bulider is not the fastest tool really, and sometimes it’s much faster to navigate through the code instead. Or maybe it’s just me? 🙂
Take a look at the following view controller (FriendsListViewController):
In this example you can see that it’s only possible to instantiate this view controller by providing its view model and view config entities. If you want to know more about view models, i.e. MVVM design pattern, you can read about it here on medium. While a view’s config is a simple struct entity which defines a view’s layout and style, like inset, sizes, colors, fonts, etc. I think it’s also a good practice to provide some default view configuration similar to this:
All view initialization is done in the setupContainerView method which is called only once upon view creation, basically just adding all needed subviews to the view’s hierarchy here and then applying their layouts and styles.
Now check out that view controller’s presentation entity:
You can see responsibility separation clearly here, and it’s not any more complicated than triggering a segue from a storyboard really.
The creation of the view controller is pretty straightforward given you have its view model set up and are just using the default view configuration:
Now let’s have a closer look at the magic happening in the layout code..
#2: Use layout anchors for simplicity
Here is the view’s layout code:
Simply what this code does is lay out infoView inside its superview at (0;0) origin nesting a superview’s size.
So why are we using layout anchors? It’s just short and simple. Obviously you can use frame-based layout and calculate all the positions and sizes on the fly, but that just can get quite messy sometimes. You can also use in-code text layout constraints as described here, but it’s impossible to use a safe-area layout guide, because this is also an error prone approach and you have to remember the text format as well:
It’s quite easy to make a mistake or have a typo in the text string defining the layout in the example above, isn’t it?
#3: Divide and conquer with components
As you can see, the UI of the sample project is split into components, each of which does one particular function and no more. Take a look at the following view from the sample project:
- FriendsListHeaderView — a view which displays some information about friends and a close button;
- FriendsListContentView — a view that displays a friends list with clickable cells, more content is dynamically loaded upon reaching the end of the list;
- FriendsListView — a container view for the header and the content views, which lays out the latter two on the screen.
I personally love this single responsibility approach, as you always know which exact entity is responsible for each particular function. It helps a lot, not only during bug fixing times (which you probably not a big fan of), but even when you develop a new feature, as this approach also significantly increases the reusability level of the code.
#4: UI and logic separation
And last but not least, my favourite high level technique, which saved a lot of time and nerves for our team, is UI and logic separation. Literally, what we have in place is a separate project for all UI components, and another project for business logic and the glue parts of UI and logic components.
Let’s take a look at the sample project. As you will remember, we have our presenter implementation looking like this:
As you can see, you need to provide only a header and a content view models. The rest is hidden inside the UI components’ implementation that I described above. A header view model protocol in the sample project looks like this:
Now imagine you are going to add some Visual Tests for your UI, and I can tell you — it’s as simple as passing stub view models for your UI components:
Looks pretty straightforward and simple, doesn’t it? Now, we want to add some real business logic for our component in our app, which may require some data providers, raw data models and so on:
It couldn’t be easier: just implement the data provider and you are good to go!
This looks a bit more complicated with a content view model, but still responsibility separation makes the life so much easier. And that’s how to instantiate and present the friends list screen in the app upon a button click:
This technique helps to isolate the UI from its business, and even easily cover the whole UI with visual tests by feeding fake data to the components! So in my opinion, the separation of UI and related business logic is crucial for the success of a rapidly developing big project, whether it’s a startup or an already established product.
Obviously, this is just a set of some techniques we use in Badoo and I’m not saying it’s a silver bullet for all possible cases, so evaluate and use them if they fit your team and projects. Of course there are other techniques such as Designable UI Components alongside Interface Builder that I explained in my other article, which we try to avoid using at Badoo for maintainability reasons. Remember, everyone has their own opinion and a view of the big picture, so I believe in order to be successful you have to reach a consensus in your team and stick to a selected approach for most cases.
Thanks for your time and may the Swift be with you!
- Full project is available on github;
- Making custom designable UI components by myself;
- iOS Architecture Patterns by Bohdan Orlov, former Badoo developer;
- Modelling state in Swift by John Sundell;
- SOLID Principles in Swift by Vinodh Swamy;
- NSLayoutAnchor by Apple;
- Programmatically Creating Constraints by Apple.