Many developers are faced with different problems related to the support and implementation of new functionality. Usually, it happened when the company or the product itself begins to grow too quickly. Undoubtedly, the user interface is growing too. As the volume of work increases, the company expands the number of developers, but this approach is not always sufficient. We will see this a little bit later. When I worked for a company that we will discuss later, we faced the same problems Robert Martin described in his books. In this article, we will consider individual developments for the iOS platform. Figure 1 shows the quarterly growth in the iOS development department. Until the 8th quarter, the initial growth of the development team needed to develop more new features. Increased recruitment of specialists began in the 9th quarter, mainly due to delays in development.
During the development process, there was an acute shortage of human resources. So the company was faced with the impossibility of simultaneously fixing errors and implementing new functionality on a quality level. You can observe this process up to the 12th quarter inclusive. From the beginning of the project, we counted lines of written code to evaluate the team’s effectiveness and other stuff. You can see in Figure 2, up to the 7th quarter, the number of code lines grew, but in the 8th quarter, the number of lines was almost similar to the previous period. And the management decided to attract new developers. Mostly they were not full-time developers.
As we can see it helped, but only for one quarter. There was a visible rise and then a similar process of stagnation. All these decisions led to the fact that the price for development become bigger and would continue to grow. The productivity of the team also became lower. The development department convinced the management of the need to consistently allocate time for the refactor and writing tests. However, the project management continued to take on too many commitments to investors, not listening to us. The turning point was the 12th quarter. After analyzing the effectiveness of the team, we analyzed our code.
We found architectural mistakes and highlighted them. They were the main reason for the inability to add new functionality without touching the old one. Someone might have made the change and implicitly created the unknown error elsewhere in the application. Also, one of the reasons is complete anarchy with the components responsible for the user interface. We carried out a set of measures to improve our program code and the development process. From Figures 1 and 2, the number of code and team members decreased, and later the amount of written code began to grow with a varying number of developers. This article will talk about the user interface changes and what solution helped us achieve the results shown and get rid of unnecessary duplication of code and increase its reusability.
Reason for the UI refactor
Our application had many user interface components. However, these components were often identical, used in a table view or collection view, and these same components could display different view models. All these components prompted us to develop the ConfigurableView approach. The main idea that we can use ConfigurableView as a separate view, as a cell for a table view, or as a cell for a collection view. And, accordingly, it can display different data models.
First, we need to add the basic ConfigurableView and ViewData protocols. Classes that implement the ConfigurableView protocol must inherit from UIView. All models that can be pass to our view must implement the ViewData protocol. ConfigurableView has a configure(viewData: ViewData) method. It is needed to pass the model that the View should display. Since we are passing ViewData, we can pass different types of the model. The following code snippet shows the implementation of the protocols.
We create ConfigurableView in conjunction with ViewData. In the configure method, we implement the display of the passed ViewData. With this approach, we do not create a direct dependence of the view on the model. Instead, each View displays an intermediate model by analogy with the VIP architecture. Furthermore, it is desirable to make all child Views private, which will allow making only one way to change the View. And this, in turn, will protect us from external changes. For example, the following code shows an implementation of a simple TitleView that displays the title and subtitle.
To expand our approach and use the same View in a table view, we create type alias — typealias ConfigurableTableViewCell = UITableViewCell & ConfigurableView. All subsequent cells for the table must inherit ConfigurableTableViewCell. The following code shows the implementation of the TitleTableView, which in turn reuses the TitleView.
Sometimes we do not need to reuse an already created ConfigurableView. Then our code will look like this:
Everything is preserved, and only the implementation of the ConfigurableTableViewCell itself changes. By keeping this approach for the future, we can quickly and easily make changes inside the implementation.
For the collection view cells, we use the same approach as for table view cells. For example, the following code demonstrates the collection view cell as a container for our ConfigurableView:
And code for ConfigurableCollectionViewCell as usual CollectionViewCell:
We also use the ViewCreator, a helper class, to simplify creating(dequeue) process of cells. To do this, we need to extend our ViewData with a new viewType property, where we pass the View type.
The implementation of ViewCreator looks like this:
We use the viewType property in ViewData to dequeue a cell with a specific view type and pass the viewData object to dequeued cell. Also, we add extensions to UITableVIew and UICollectionView, that help us dequeue cells by viewType. With view creator, all that we need to do in our delegate method display the following code snippet:
Do not forget to register your cells in table or collection views
For the convenience of casting the model type, we added the modelFor<T: ViewData> (_ type: T.Type, _ viewData: ViewData) -> T? method, usage of this you can check in early in code snippets frames. Below is the implementation code:
In general ConfigurableView approach was our big step in cleaning and refactoring our extensive application. It helped us separate the UI module from everything in the project and make it independent somehow. In addition, we developed our code style to create views, increase reusability where we need it, and make it clear to understand. But this is not all that we have done to achieve such results.
Follow me to receive more interesting practical information about iOS development.