Cells creation in iOS

Timur Shafigullin
hh.ru
Published in
11 min readMay 3, 2022

It’s a typical day of a mobile apps developer: received json, parsed, rendered cells on the UI, PROFIT.

How to conjure smartly with cells without reinventing the wheel we told in one of the episodes “HHella cool stories” and then made an article out of it.

Let’s find out what cells can be, look at the cells in the design system hh.ru and their fulfillment in the code and try to create our cell.

Actually, these are cells.

Cells can be constructed in a different way. The first method is table. The cells are built vertically one after the other and can have sections. As a rule, there can be no indentation between the cells.

The second method is collections. These collections have multicolumn, more flexible configuration and they can have spacing between sections and between cells.

We have refused the tabular layout and use only collections. All our new screens have already been built using UIcollectionView only.

A number of problems can appear during assembling a screen with cells. One of them is when a designer gathers a new screen, comes and says: “Her, I made a cell, added some elements and arranged them a little differently”.

Due to this a lot of the same type of code is created each time, the cells are created anew, and we cannot reuse them. Because of this the screen assembly speed is also significantly reduced.

To solve this problem, we decided to add our cells to a design system in order to somehow be able to standardize it. I would like to start with indentation in the design system. We will operate on them in the following slides and in the code.

The indentions in the design system are listed from 2 to 32 and have the following set of constants.

These indentions are used in layouts. Designers use a Figma plugin to easily enable and disable on layouts. Developers can also clearly see what the indentation between the elements is.

In the code all this splendor looks like a simple protocol. It implements all the standard numeric types of Swift. The protocol itself has an extension with static variables. When we build a layout that means we make a layout we use these indents through a dot. We can point out the inserts xs, m and others.

Let’s go back to the cell. The cell itself in the design system is not quite simple. Let’s take a look. We have a title and we have chevron. Our cell represents the left and right parts. On the left part there is a content that has an indent on the left — m, on the right it is zero. That means the left part is pinned to the right.

The right part has an indentation on the left — xs, and on the right — m. The left part is not just pinned to the right because right part can be missing.

Not any content can be placed in the left and right parts. The set is limited by a certain list which is shown below:

For example, in the right part we can put only detailed descriptions, chevron, badges, toggles and something like that.

Combining all the combinations left and right parts we can get a summary matrix. It consists of a set of components in Figma, designers reuse these components on layouts that is convenient and visual to developers. We have collected all this matrix in our code. Next step we will see how it works in the code.

In the design system the cells allow not to duplicate elements in Figma and to reuse them. In turn, we do not duplicate the same type of cells in the code as developers.

And the last thing is that synchronization between developers and designers remains at the level of communication. It is enough for the designers to inform the developer about changes in the cell component so that the team can update the code.

Container cell diagram

The container cell is a ContainerCell that inherits from UICollectionViewCell. We have one for the whole project.

This cell has a ContainerContentView inside. It’s a simple protocol which can be implement by any UIView. That means we can put any UIView which implements this protocol in the cell.

This protocol is also related to another protocol — ContainerContent. ContainerContent is a model for the UIView that configures it. ContainerContentView is associated with ContainerContent through associated types, i.e. ContainerContentView has associatedtype Content. In turn, ContainerContent has associatedtype View. That’s why we create a one-to-one bundle.

Let’s go farther. There is also a ContainerItem cell model. The container cell model knows about its cell and what content is placed in it. This is because ContainerContent has associatedtype View. Also the cell itself keeps this model indside and helps us somehow process the action from the cell and configure it.

Code time

Now when we found out about a general diagram we can delve into the code. As mentioned earlier we have ContainerCell where has the generic ContainerView type with the ContainerContentView protocol. This protocol should be implemented by any UIView that will be places in the cell.

The ContainerContentView has an associatedtype Content, it also gives its size and can be updated with content. The UIView dimensions are needed so that we can know the size of the cell itself.

ContainerContent is an empty protocol its only requirement is that its implementation also conforms to the Equatable protocol and it has associatedtype View. With the help of this bundle we get one-to-one connection. One ContainerContentView can only have its own Content, and one Content can only have its own View. Simply speaking each View has only its own model (Content).

Let’s go to the cell. Our cell has its own model, it’s called ContainerItem.

It contains some properties. Firstly, it knows about her cell (ContainerCell) and which UIView (ContentView) is placed there.

The cell model also contains Content from this View. This is necessary for the configuration View that is placed inside the cell.

Also, in the model cell contains properties like:

  • differencеID — we need it to calculate the diffs in the collection;
  • accessibilityIdentifier — needs fot UI-tests;
  • insets — allows you to indent inside the cell itself;
  • isEnabled — enables/disables the cell. In the disable state, clicks are not processed and the cell itself become translucent with an alpha of 0.5;
  • isSelectedBackgroundNeeded — enables/disables background selection when clicking on the cell.

And finally: all the necessary handlers are located here. This means that we can handle pressing, long pressing, displaying and hiding a cell.

Ceneric containers are very convenient. They allow you not to duplicate the logic of cells. We use one cell which any UIView can be placed. This UIView can also be a container with a generic type. That is we can setup a container in a container.

And here we can find the main advantage: we can build a UIView and use it even in a cell, even just on a UIViewController. UIView itself does not know where it will be used.

Your personal cell

Now we will assemble our cell. To begin with let’s remember the cell which we considered above: Title+Chevron. Let’s assemble a cell using this example and at the same time we will find out how our cells from the design system are arranged in the code.

Let’s create a common container — this is a usual UIView, in which we can place left and right part. Also, we will add a separator here.

LeftContentView and RightContentView protocols have appeared and also LeftContent and RightContent protocols for them accordingly. They are similar to ContainerContentView and ConteinerContent the only thing for LeftContentView and RightContentView is added a method to determine the minimum width:

Let’s create a content for LeftRightConteinerView (our ContainerContent) — it is also generic content for left and right parts is placed in it. The separator type is also specified.

In our ContainerItem we put the LeftRightContainer with the left and right parts. To make it easier to use in code, wrap it in type alias and call it LeftRightItem.

Now let’s assemble the left part. The left part looks like a usual title. This is usual UILabel, we will configure layout, arrange all necessary indents — nothing special.

For this UIView we create content and add all necessary properties to it — accessabilityIdentifier, title (which we want to put down), number of lines and style.

Now all we have to do is implement the protocol for UIView and update the content. That is our content model updates our titleLabel.

For the right part, everything is a little simpler. We create a separate UIView and put there a UIImageView with a static chevron image. And because of this, our model remains empty.

Code of content model:

Now we put the LeftTitleItem and RightChevron in the LeftRightItem (typealias announced earlier) which we have just created. We will wrap this also in typealias for convenience and call it TitleChevronItem.

Consequently, we can assemble the entire matrix that we have seen before with all the left and right parts.

To summarize: what do I need to create a cell with my content?

  1. Creare UIView;
  2. Create a content. It will be our model for UIView which will configure the display with the ContainerContent protocol;
  3. Our model must be Equatable — it is necessary to calculate the diff of the collection in a case of changing the model;
  4. And for our new UIView it remains only to implement the ContainerContentView protocol, which returns the required height and updates the displayed content.

Demo

The designer came and said: “I have assembled a new screen. We need to do it tomorrow.” Ok, no problem. The designer assembled the screen from design-system cells.

Our first cell has a picture, title and subtitle (Image+Title+Subtitle). The second cell has a title on the left side (Title) and on the right — a detailed description and a chevron (Detail+Chevron). The second section, the menu section, consist of an icon and a title (Icon+Title). The right side is empty. And the third section is the PUSH notifications section. It consists of a checkbox with a title on the left side (Checkbox+Title) and an emptiness on the right.

Let’s try to put it together in code. In our project we use the MVVM architectural pattern. As a rule, we have business state and UI state. StateMapper Converts the business state (there is none in this example) to the UI state (CellListViewState).

So, we have a CellListViewState and we need to return the state of the collection. The state of the collection, CollectionViewState, requires returning a set of some sections. Let’s create these sections and describe them declaratively.

The first section is the main one, let’s call the function for creating the first section makeMainSection() and return the section.

But what CollectionViewSection is?

CollectionViewSection represents a structure describing a section of the collection. It has several properties:

  • differenceIdentifier — requires for the collection diff;
  • header and footer — structures that describe the header and footer section accordingly;
  • items — structures that describe a collection cells;
  • layout — structures that describe the layout of the section. These are margins between cells, sections and column settings. The column can be adaptive (the number will depend on the width of the screen) and fixed
  • extras — this is a property where you can put any structure or class that implements the CollectionViewSectionExtras protocol. It only affects the calculation of the diff.

Now we are collecting. For the first section we will need only items. How do we assemble from the design? The first cell we can see it is called Image+Title+Subtitle. And on the right we have Chevron. We will write like this.

Here we will need to configure only the left and right parts. We will write the LefImageTitleSubtitle on the left and configure it as we need. Let’s put down the pictures. We have a default avatar it will be round. Let’s take the title and subtitle from the design. In the right part there is a chevron, so we write: RightChevron. The model is empry we need only to initialize it. We also have a separator here. The separator has insets from left and right — 16.

The second cell represents the country of the search. It is a TitleDetailChevron. We will configure the right and left parts. The title on the left we write: LeftTitle and configure. On the right Detail+Chevron. We point out the title from the design.

We do not have a separator so we can safely put none here.

Great, we have assembled the first section. Now you should do the same with the second one.

In the second section we have already had a cell with an icon and a title. Here we put down the left content, as we haven’t the right one — we omit it.

We also need to configure the layout. It allows you to configure the minimum margins between cells, columns and place some insets. Insets are exactly what we need. According to the design we will have an L inset at the top and bottom and 0 on the left and right.

And we need to build the last section of PUSH-notifications. Here we have a title (header) of the Large type. Our headers are also included in the design system which allows us to easily reuse them in a code.

Now it remains only to write the sections and give them to our state.

Now let’s try to build and see what we have done. So, we had a sections with the username, country and PUSH-notifications.

But if we want the cell to be pressed, we need to implement a didSelectHandler. Here we can handle and perform some actions by clicking the cell.

After we add didSelectedHandler closure, our cell can already be highlighted.

Conclusion

Now designers can quickly assemble screens from ready-made component cells. We have moved away from duplication in the code and can easily assemble a new cell. Building screens has become much faster.

This whole story is incompatible with xib and Storyboard. For some it may be a plus, for some it may be a minus, but for us it is rather an advantage. Since we don’t use xib and Storyboard, it will be a little easier for us to switch to SwiftUI.

This article is based on one of the episodes of our video podcast “HHella cool stories”. It can be viewed here.

--

--