SwiftUI and architectures: VIPER and Clean Swift

Danilo Charântola
Movile Tech
Published in
6 min readFeb 12, 2020

--

You probably already know that, but this article is part of a series of articles about how to integrate SwiftUI to different architectures. In the previous ones, I covered MVC and MVP, and now I will talk about Viper and Clean Swift.

Until now, I was covering one architecture per article, but since these two architectures have very similar components and the changes required to integrate them with SwiftUI are basically the same, I will show a detailed example of how to integrate with VIPER and then give a simple explanation about Clean Swift.

I will explain everything I judge necessary for you to understand the architecture, but some prior knowledge about iOS Architecture Patterns and Combine framework might be nice.

But first things, first. How these architectures work?

VIPER

In VIPER, we have five components with the given responsibility:

  • View: renders the screen and received user actions. When using UIKit, UIViewController is part of the view.
  • Interactor: responsible for business logic. It decides how to handle actions, access workers or data providers to fetch data, call persistence managers to persist information, and tell the presenter what should be presented.
  • Presenter: receives actions from the view and send to interactor to handle it. Then receives model objects from the interactor and convert into ViewModels, which are send to the view to render. View models have only informations required by the view.
  • Entity: simple model objects that holds data and don’t perform any logic.
  • Router or coordinator: controls navigation, instantiating the next screen and its components.

So, the data flow in viper is basically that:

There may be some variations on which component has the reference to the coordinator. It can be in the ViewController, interactor or presenter.

Clean Swift

In clean swift, the components are the same, but the way they communicate changes. The view send actions directly to the interactor, instead of the presenter. Consequently, we have an unidirectional data flow between View, Interactor and Presenter:

Again, we can have variations on which component has the reference to the coordinator.

Components interface

In both architectures, the communication is done using protocols, so it became easier to test each component, and to control the exposed methods.

I like to name the interfaces according to the pattern in the following example, in which we have a shopping cart view with:

  • a list of items
  • an order summary
  • a button to finish the order.

If the user is logged when the button is pressed, it finishes the order, otherwise it shows a login screen.

Some people prefer to call the interfaces following the pattern ShoppingCartPresenterInput instead of ShoppingCartPresenterProtocol and ShoppingCartPresenterOutput instead of ShoppingCartPresenterDelegate.

Others prefer ShoppingCartViewToPresenterProtocol and ShoppingCartPresenterToViewProtocol respectively, indicating the communication direction.

Feel free to choose.

The OrderWorker can be implemented using the Combine framework, as we did with DataProvider in the MVC tutorial.

Presenter and Interactor

Regarding to the interactor, I would implement like following:

and the presenter would be:

Pay attention to the strong and weak references to avoid memory leaks, and check how the presenter has methods to convert model to view models (contentViewModel(from order: Order) and errorViewModel(from error:)).

Also, it is possible to notice that the interactor manages the business logic, deciding what should be done and what should be presented.

View

Now, regarding the view, I would follow the same approach as the previous tutorials, replacing the UIViewController with a Store, that will be the presenter delegate.

Note how the store just send the received actions to the presenter in the methods fetchOrder and performAction.

Also note that the render methods just update the state property, whose changes are published using Combine framework and making the view reload, because it has the store as observed object:

Coordinator

Now, for the last part, we need to implement the coordinator, which are a little trick and has different ways to be done.

The first possible approach is something like that:

First, we have the associatedView method, which create all components, connect them and return the view to be presented.

Then, when we need to present another screen, like the login screen in the example, we need to create the corresponding coordinator, get its view and present it.

And, in order to present the initial screen, it is necessary to do the same steps in the SceneDelegate.swift:

Note that we are not presenting the SwiftUI View directly. Instead, we are embedding it in a UIHostingController. And the first UIHostingController is embedded in a UINavigationController.

This way, we have a UIViewController with a navigation controller, and it is possible to use methods like present, push and pop ViewController to control navigation.

Of course, this isn’t an ideal approach, since we are going back to UIKit instead of using SwiftUI. The reason for me to present this approach is because SwiftUI doesn’t provide (until now) a good way to control the navigation stack, and the only way to navigate is using NavigationLink, making it hard for the coordinator to present new screens.

However, it is still possible, as we are going to see next.

Coordinator (second approach)

First, we have to use a NavigationLink in the view, instead of a Button:

Second, we have to add navigation information to our view model, so the view can observe it and navigate when a shouldNavigate flag changes to true. Look again how the NavigationLink was created in the previous example.

Third, the coordinator has to delegate to the Store the responsibility to change the navigation view model, and trigger the navigation:

And finally, the Store needs to implement the coordinator delegate:

Comparing the approaches

The second approach works and uses only SwiftUI, but has a few downsides:

  1. The view needs to know if a button can trigger some navigation or not, in order to create a Button or a NavigationLink. This way, the view will have some business logic, which is beyond its scope.
  2. For each button that can trigger some navigation, we need a corresponding navigation view model.
  3. There is no way (that I’m aware of) to perform actions like pop to root, or pop to view controller using SwiftUI elements (you need the UIKit approach for that).
  4. And last, the NavigationLink used in the example is not working very well and has a bug described here, that will probably be fixed soon.

I believe that Apple will provide a better way to control the navigation soon, but by now, I recommend you use the coordinator pushing and popping UIViewController.

Passing data between screens

One final point is how to pass data between screens. For example, imagine we need to pass the order to the success screen. In this case, the associatedView method of the SuccessCoordinator can receive the order and inject it on the interactor:

And the presentSuccessView would be:

Final architecture

Summing up, the VIPER architecture using swiftUI looks like that:

And, if we use Clean Swift, the integration with swift UI will require pretty much the same changes (replace the ViewController with a Store, and implement the coordinator using one of the previous approaches).

The difference between the architectures will be how the View, Presenter and Interactor communicate with each other. But it is related to the design of each architecture, and not the use of SwiftUI. The final result should be something like that:

That is it. Please, let me know if I helped you.

--

--

Danilo Charântola
Movile Tech

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