SwiftUI and architectures: MVC

Danilo Charântola
Movile Tech
Published in
7 min readJan 13, 2020

--

One of the most exciting news at WWDC 2019 was the introduction of SwiftUI, which completely changes the way we create views in Swift, settling in the middle of the discussion between view code and storyboards, and taking the best of both worlds, with its declarative syntax and live preview.

But it’s not all.

SwiftUI impact is not limited to how we create views. It will also change the way we architect our code.

This is the first article of a series about SwiftUI and code architectures. In this article, I will show you how to migrate to SwiftUI an app developed with UIKit and MVC, without changing too much its architecture. And then, I will give you some hints about how to avoid the most common problem with this architecture: the massive view controller.

So let’s get started.

MVC

If you are an iOS developer, you have probably heard about MVC, the Apple oficial architecture. But if you haven’t, don’t worry.

In MVC we have the following components:

  • Model: Performs network request, loads data, parses response, persists configurations…
  • View: Responsible for rendering screens and receiving user actions. When an user taps a button, the view informs the view controller.
  • ViewController: Receives user actions from the view and handles it, calling the Model layer to do whatever is needed, and then updating the view and restarting the cycle.

But then, when we talk about SwiftUI, we have a huge change.

There is no View controller!!

Replacing ViewController

So, how can we make some kind of MVC if we don’t have a ViewController anymore?

Well, another change introduced with SwiftUI is the use of Data Binding and Combine framework, giving a reactive approach. And we can use the concept of Store, an object that, as the name suggests, stores the data/state of the app, and is observed by the view, telling it to update whenever some data changes.

In the proposed architecture we will replace the MVC UIViewContoller with a Store, ending up with something like this:

It is time to see some code!!

First, we have created a simple Item object with a few properties and that belongs to the model layer.

Then, a ItemDataProvider, also belonging to the model layer, and responsible for performing a url request to get the items. In this case, using the combine framework to give an async response.

A Publisher is basically something that publishes new values to the subscribers everytime the data changes or new data is available. It has two associated types: the type of the data to be returned (an item array in the example) and the type of error that can be thrown (in this example, the publisher never fails, because it replaces errors with an empty array).

We also have the ItemListStore, that calls the data provider to load data, as well as the ViewController should do in the MVC architecture. Then, when it receives the items, it updates the local items property:

A big diference here is that the store does not need to explicitly ask the view to reload. Since the items are marked with the @Published property wrapper, as soon as it is updated, the changes will be published using the combine framework and the view will be reloaded.

And finally, we have the view, which contains a reference to the store and calls the fetchItems when it appears.

With the above example, I’ve covered just the fetching scenario, loading the data when the view appears. In the case of an user interaction, the flow would be pretty much the same:

The view receives the action and informs the Store. The store calls the model layer (a dataProvider, a service, a presistenceManager or anything else) to perform some action, then it updates itself and makes the view reload.

And, regarding to navigation, we replace the use of storyboard segues with the use of NavigationLink, as seen in the example.

Avoiding Massive Stores

One of the biggest problem with MVC is the well known Massive View Controller. Any search about this architecture, and you will probably find someone joking that this is what MVC stands for.

But, recently, I’ve found the following statement in this article (Much ado about iOS app architecture)

Why does every single article, describing any of these new architectures, starts with a variant “Apple’s (sic!) MVC leads to a bloated M(assive)VC”.

When I read that, I see: here’s another wannabe who never bothered to learn how to properly Delegate. Who never learned to use container controllers and compartmentalize functionality into embedded controllers.

PSA: No one is forcing you to implement multiple DataSources in one Controller. To initiate network calls in viewDidLoad. To parse JSONs in UIViewController. To hard-wire Views with Singleton instances. If you do that, it’s your fault; don’t blame MVC.

This is a hard, but very insteresting affirmation. It bothered me, as it is possibly bothering you now, and made me think.

In the end, I came to the conclusion that MVC is a conceptually simple architecture, easier for beginners to learn and start producing something. But it is also very easy to make a mess.

In order to make it work, you need to be very disciplined, and have good knowledge about how to decompose your view and divide responsibilities, since it is not very clear in the 3 main components (model, view and controller) how to do that.

Other architectures, like VIPER and Clean Swift, have more components with clear and specific responsibilities, that might be harder to understand at the beginning, but after that, you are more likely to have an organized code, without very long files with a lot of different responsibilities.

To make things work with MVC, you have to know how to organize your model layer, having different subcomponents to handle network request, parsing, data persistence, calculations and business logic. None of these things should be in your View controller.

You also have to pay attention to your view. It should only be responsible for UI related stuff, and should not receive model objects to configure itself.

And finally, you need to know how to organize your view controller, and understand that one screen does not necessarily means only one controller. You can, and sometimes you should, split your view in subviews with container view controllers. This way, you will not end up with a single and gigantic view controller as delegate and data source of several views at the same time. This article (A Better MVC) shows an interesting way to do that.

Returning now to SwiftUI, we need to apply the same discipline. The model should be organized in the same way, and our view should be composed by smaller and organized subviews, with also small and organized stores.

If any file in your project is getting too big, it is probably doing too many things and should be splited in smaller components with single responsibility.

Even in our previous very simple example, the view is doing two things. It is rendering the list of items, and it is handling actions, calling the store to fetch data on appear.

Decomposing our view

To fix this, we can split it in two views like this:

Note we also have added the option to remove an item very easily.

One last point is that, in SwiftUI, views are structs and use composition, instead of being classes and using inheritance as in UIKit. As a result, the views are very lightweight and it is highly recommended to create as many simple and reusable subviews as possible.

So, as a final example, imagine you have a screen in your app for a shopping cart, and you want to present:

  • A list of items
  • Button to edit items quantity
  • Button to finish the sale
  • Carousel of suggested items
  • Loading view
  • Error view
  • Empty state (if there is no item in the shopping cart)

This is a lot of information to control and if you put it in a single view, with a single controller or a single store, you will end up with a mess. To avoid that, you can divide the screen in the following views:

  • ShoppingCartView (for the whole screen)
  • SuggestedItemsCarouselView (for the carousel)
  • CarouselItemView (for an item in the carousel)
  • CartItemsListView (for the list of items in the shopping cart)
  • CartItemView (for an item in the cart)
  • QuantityEditionView (to edit the item quantity)
  • FinishSaleButton (to finish the sale)
  • LoadingView
  • ErrorView
  • EmptyStateView

And you can also have different stores, like SuggestedItemsStore, CartItemStore and PurchaseStore.

This way you will have much smaller and clearer components.

--

--

Danilo Charântola
Movile Tech

iOS developer. Interested in code architecture, algorithms, new technologies, movies and series.