Build your cells in a way of LEGO
Update: Please checkout LeeGo on Github, a lightweight Swift framework that helps you decouple & modularise your UI component into small pieces of LEGO style’s bricks, to make UI development declarative, configurable and highly reusable.
Since Apple released iPhone 6, iPhone 6s, multitasking of iOS9 on iPad and now iPad pro, people finally begin to talk more and more that how to build an app full responsive. In a native way, support the whole range with pixel-perfect design is difficult, and it’s quite a big topic. Cause the apps can be so different between each other (ex: a gaming app versus a news app), this will lead a totally different approach to archive responsive design. In this blog post, I’ll try to focus only on one of the most commun cases: app consume the json data flow from backend which include zero to x items, then present items into cells of collection view.
Let’s see how we can achieve full responsive app in a way of LEGO and more important, hassle-free.
TL;DR, You can checkout the whole sample project on Github.
Before all, checkout what’s the full responsive we are talking about:
This is from iOS app Le Monde, l’info en continu, width of collection view is changing from 300pt to 1024pt. Every pixel between this range is covered by one of the 5 levels (XS, S, M, L, XL) of size, which is dedicated with all 5 different designs.
What’s the problems we are dealing with in a traditional way implementation ?
Speaking of collection view, surely you need to implement collectionView:cellForItemAtIndexPath: somewhere, right? It will probably looks like:
Then in configureWithItem method, we manipulate appearance or layout of cell based on different item, use autolayout or not, use size class or not, use IB or not, right? It’s OK if we are dealing with some simple data models. How about a real world application? Well, it’s just way more complex:
- For so many different type of item: in app “Le Monde”, we have a dozens different types of item for different purposes. (I suppose it’s probably the same for other apps who need to present the complex information, such as app of NYT, Twitter or even Airbnb).
- For so many different positions: sometimes we need the same item has different appearance if it appear in different position.
- With so many different screen sizes: Today, on iOS platform there are at least 8 different screen widths to cover. Actually we don’t really care, as we talked at the beginning, we want to have somethings turly full responsive, right? Which means we need to cover every pixel from 320pt to 1366pt. 😉
- With different additional conditions: such as, did user already login, or did user opt on the night mode, etc..
We need to present our cells with different appearance and different layout for all those combinations. And even more:
- Cells should have dynamic height, we don’t want to truncate text at all.
- Some component inside could be missed sometimes
- Some cells need to be dismissed in some circumstances, such as an advertisement cell be failed to load.
What’s the idea here?
When we build a cell, it always requires only two parts of work: 1. setup appearance and layout of all components inside based on a design style 2. update content with the business object (a.k.a item). The idea is completely isolating the codes of first step out of the cell without any coupling, put them into a JSON style configuration file, make them configurable in an ASCII world.
Let’s build it
I. Create our “LEGO” components. There are 4 components in this example: title, subtitle, date and avatar. Avatar represent by a subclass of UIImageView, the rest are represent by a subclass of UILabel(we’ll have an example later). Component could be any custom UIView’s subclass, as long as it could be added as a subview of cell.
II. Declare our component styles. In the example, we use only the appearance from UIAppearance. We could also have our own custom appearance if necessary.
III. Implement component class, this is an example of “title” component. It should conform to CellComponentProtocol.
IV. Create cell’s configuration. 1. list of components. 2. list of component’s style. 3. define layout by using standard visual format language of auto layout.
V. Then we get into the most interesting part: retrieve configuration of our LEGO components, then combine them together in cell.
- Retrieve configuration correctly based on reuse identification.
- Instantiate each component view based on class listed in configuration.
- Setup each component view with style listed in configuration.
- Layout each component view with auto layout visual format language from configuration.
- That’s all 🎉🎉💯🍻
So, let’s put all the pieces together. We will get a cell like this:
You may think this is not a big deal, just a cell with an image and three labels. Well… what if we need to add new design or change some design for different screen size?
Now that’s will be really easy, evan our designer know how to achieve it 😝. More seriously, all we need to do is modify a little configuration file.
Now we’ll have two more completely different cell designs, without modifying our cell view, or touching IB files. You can easily choose what’s component you want put in the cell, and how to layout them together. Actually this is all about adding/modifying some ASCII constants. Based on this approach, we could get out of the mess when we deal with full responsive design.
What’s the real advantage in this way ?
Short version, this way brings us three key advantages:
- Declarative: you actually “declare” your cell’s UI by ASCII constants instead of “develop” it by code. Even declare the layout.
- Composable: every component in cell is reusable. You just choose what you want to be layouted in a cell
- Configurable: style of the component is completely configurable by tweak some ASCII constants in the configuration file.
What about iOS standard size class ?
Since iOS 8, we can use size class to adjust our UI based on different class: compact & regular. Which means we only have two level. For our app, or most content-presentation based apps, two level is just not enough to cover all the different size and make the prefect design.
Why use Visual Format Language to do the auto layout?
Again, let us have capability to make full declarative UI development.
Where to go next from here?
- Support dynamic height.
- Use the similar approach to layout our cells in collection view.
- Since configuration file is basically json-like, we modify and add new design only using ASCII constants. Theoretically, we can put all those stuffs to the server side, and change your app content’s presentation remotely, that will be really cool.