MVVM with UITableViews — Using forms (part 2)
This is the second part of the story for using MVVM pattern with UITableViews. First part can be found here:
A story of MVVM with UITableViews (part 1)
This is a story of an implementation of MVVM pattern in table view controllers. It’s using UIKit, released a bit late…
Let’s continue developing our places app! So, as we saw in the first part using read-only data looked pretty neat. We are happy with the implementation till now. But as I already said… code can never be perfect and there isn’t only one “right” way.
The problem for us arose when we started using forms, where we need the user input also. In our application we had pretty complex input screens. With this setup it was difficult to keep the view and view model in sync, and also the code to be understandable and easy to read. So… we made a bit of a mess - this is the point when things started to become not so perfect.
This could probably be done better with using some of the libraries like RxSwift but then we decided not to include anything additional, especially not complex frameworks.
Let’s see how we can make messy forms! In the sample app I tried to implement a simplified version of what we had to deal with in the real app. Imagine that the logic was a lot more complex than this example.
As I said it is a complex form:
- It contains multiple sections and different kinds of input cells: picker, switch, text field cells etc.
- One section can be shown or hidden depending of values chosen in another section.
- Every section will have validation on it’s cells
- Each section might have some logic for showing / hiding cells depending of the category of the place shown.
First we went with having all the logic in the main view model, however it turned out pretty big and incomprehensible, so we needed some more granular separation. After rewriting the code for like four — five times we finally settled up on the following solution:
Every change from the cell view propagates to the main view model. The main view model then needs to update its sections and notify the view controller to refresh the table or show some input view. Yes, the main view model still has many responsibilities, but at least the validation and the logic is handled by the sections.
Here I want also to mention that the initial idea was to use structs for the view models. They would not be modifiable but instead we would always make new view models depending of the data and the state. That could still work for the showing of the data like we had in the first example, but for forms we need to keep the state, we can’t always remake the whole view model and reload the whole table view. For example: the user is typing and we want to validate the field while typing so we know whether to enable or disable the submit button, we need to only refresh the button section because otherwise if we reload the whole table the first responder field will lose focus and the keyboard will get dismissed while the user is typing.
That’s the concept — let’s see how the code looks like. Now we will extend our places app with implementing input form about a place. The form is used for editing a place and also adding a new one. It contains the following sections:
- Base info section, contains the name, description and category for a place.
- Place details section has many details about the place like length, width, area etc. Depending of the place of the category (river, lake or mountain) has other properties specific to it.
- Location info section, contains the country and a switch for whether the forth section for coordinates should be shown.
- Coordinates section has two fields for the coordinates of a place.
- Button section is just the button cell for submitting the form.
The new classes for this implementation and their responsibilities are the following:
- PlaceDetailsVC — the main view controller for the form. Handles strictly the UI things: registering cells, updating view, handling keyboard and picker view (~120 lines of code).
2. PlaceDetailsVM — the main view model for the form.
- Sets up all the sections
- Handles all updates from the cells it knows whether to show / hide a section or just propagate the data to its corresponding section.
3. Sections view models: BaseInfoSectionVM, PlaceDetailsSectionVM, LocationInfoSectionVM, CoordinatesSectionVM and ButtonSectionVM. They usually contain 4 parts:
- Properties for the cells data
- Computed properties that generate the view models for its cells
- Validation of the fields
- Updating of the data properties
4. Cells and cell view models: FormCell, SwitchCell, PickerCell, ButtonCell
- Same concept as previously except that these cells contain delegates conforming to PlaceDetailsVMUpdateDelegate for sending updates when the user enters text, flicks the switch or clicks on the picker label.
Those are all the classes based on the same template, it still has good structure — we always know where to look if there is for example an issue with the validation or if some cell is not shown or updated. Also don’t forget the thing I’m the most exited about — unit tests! We still get to test this all (except maybe those 120 lines of code in the View Controller :) ). Now you don’t have to be afraid to refactor something in your view models, the tests should catch most of the bugs that could appear. If you check the full code there are many tests, some examples are the following:
Just as a reference we have almost 85% coverage on our Places app, that is more than 1500 lines covered from total of 1807 ! I even added some tests of the setup of the view cells and for our base classes. If we write good tests and cover all the cases our app will become nearly bug free (at least from logic side, the UI is sometimes problematic). From the report below you can see that the only places where we have low coverage are the view controllers and cell views where is mostly UI stuff which we don’t have to test. Our view models should always be 100% covered.
I kinda like the current design, there is separation, every class has limited responsibilities and also we have amazingly high testability. The only thing that bothers me is all those update methods in the main view model. Currently everything has to go through that class..and imagine if we had even more complex logic. This could probably be improved when we would have some kind of binding between the cell view models and their cells, so then would be possible that the section data is automatically updated without calling those annoying delegate ‘update’ methods. The next steps would be exactly that, to try to improve this implementation with some kind of binding and also try to use Combine.
Another thing I‘m trying out is to see how this same app can be easily converted in a SwiftUI application without making big changes to the view models that contain the logic.
Stay tuned for the next improvements to our Places app!
Full source code can be found here: