When developing screens containing several user interface (UI) components that represent and modify the same underlying data, it is crucial that we do our best to keep the view code as simple as possible. It can be tempting to take the “easy” route and discard good architecture and maintainability, especially when there are tight deadlines. However this will only cause more work down the line and can be easily avoided.
That is why in this post I will demonstrate how using a view model simplifies, and actually speeds up, the task of developing complex screens in SwiftUI while also increasing our view code’s readability, maintainability, and scalability.
Swiftly developing a first draft
Suppose we need to develop a screen for controlling an air conditioning (AC) unit like the one displayed below. In the top portion of the screen we have all the controls, and in the bottom half we simply reflect the current state.
In this screen we have the following functionality:
We can turn the unit on/off
We can toggle the AC between heating and cooling
We can set the temperature using a text field as well as a slider
We can toggle the fan on/off and set the speed.
At the bottom half of the screen we have images representing the current mode, fan speed, and a progress bar reflecting the current temperature progress to our desired temperature
When developing a screen like this, or any other screen for that matter, before I write any view code I like to start with the view model. It is a good idea to first determine what data the view represents. This screen happens to represent and control the AC’s power, mode, desired temperature, fan power, and fan speed. Using this information, here is the view model I implemented:
In this view model I have all the data and state information I will need in my view. What ever state and data is in this view model can be easily shared between different views by sharing this view model.
Here is the initial implementation of the view code I quickly developed using my view model, we will improve this later:
This view code accomplishes what we want, however this is not easily readable, maintainable, nor scalable. In its current state, the view code is very long (115 lines) and difficult to follow. Ideally we should be able to look at the code and easily locate the different sections of the screen and know what each one does. This makes it easier to maintain and to learn for new developers joining the team.
Improving the code
In order to improve our view code we should analyze our screen to see how we can break it apart into smaller logical components. We should be careful not to break down the screen just for the sake of creating smaller components, ideally a UI component is made of related UI elements that make sense to be put together.
After analyzing the screen for some time, I decided to group the UI elements into the following five groups displayed below.
The groups are as follows:
This group controls the AC’s mode. We could move these into their own view component and call it AirConditionerModeControlsView.
The two control views here adjust the desired temperature, so we could group them into a new view and call it AirConditionerTemperatureControlsView.
These views control the fan, so let's group them into a new view called AirConditionerFanControlsView.
The views in this group reflect the current operational status of the AC, so they could be grouped under AirConditionerOperationalStatusView.
Lastly, these views belong together because they are used to display the temperature progress of the AC, so they could all go under a new component called AirConditionerProgressView.
Below you will see why it is a good idea to put as much state, especially shared state, in the view model, as opposed to using something like a @State property within each view.
Here we have the simplified AirConditionerView file. Notice that because each view uses the view model for its state, all I had to do is pass the view model along.
As you can see, the view code is broken down into the separate components I described earlier and is much easier to look at (only 24 lines of code). Breaking down the view like this makes it easier to see where each component on the screen is and where new components belong.
Following are the smaller view components. You will notice that I didn’t need to edit them much if at all:
By using a view model I was able to encapsulate all of my view's data and state in one class, which I could then share across different smaller views. By breaking my initial large view into smaller view components I made my code cleaner, more maintainable, and scalable. It also did not take much effort as all I had to do was pass the view model along. So next time you are developing a complex screen, remember what you learned here and give it a try!