VIP[ER] in SwiftUI
Hi! I think you know about VIPER. It’s really awesome architecture for your iOS app. It is very powerful in testing and helps you to create an incredible app which can be supported with a billiard feature.
But in 2019 at the WWDC 2019 Apple introduced a new way to create the UI in your app — SwiftUI. It’s an extremely simple and fast way to create a UI. But how does it work with SwiftUI? How do we not lose SwiftUI feature and create testable modules?
I and my friend created an app for our pleasure. And we want to create our app with SwiftUI. Our app with UIKit framework was written on VIPER module using Generamba for generate module (it’s a really powerful tool for the generation of modules. You can check our template for this one). When we create our new template for VIP[ER] in SwiftUI we wanted to get all SwiftUI’s opportunities in our app. And I think we have reached the goal. Maybe.
Let’s start!
Let me show you some example of an app which just generates new String value for an array and shows it on the screen.
In the beginning, we create ViewModel for module.
ViewModel contains obligatory field error which is used to detect errors. How and why? For example, we create some enum that adopts an Error protocol and add case .emptyState when our List (UITableView in UIKit) must show empty holder. Then we add needed property. For our app, we need ListModel struct which contains String listTitle property. Class ListViewModel adopts an ObservableObject protocol. It allows us to create a reference to this model from Presenter and View part of our VIP[ER] module. For support ObservableObject we should import SwiftUI and Combine. Now let’s talk about a View part.
View part
Let’s slow down again.
We have:
- Presenter force-unwrapped property for Presenter (Force-unwrap is used because Presenter property can’t be nil).
- Router property. What is router? Let`s talk in more detail.
Back to the beginning. I wrote the name of the architecture using brackets (VIP[ER]). Why? Because SwiftUI changes game’s rules. You can’t use navigationController.push… for push to new screen. Now you can navigate in your app with NavigationLink with destination View struct. And this is because we can’t have Router (or Wireframe). We use navigation in View part. And for assembly of our modules our app has DIResolver class which creates Presenter, View, Interactor and ViewModel for new module. What does it look like?
It is generated by Generamba too. In this example we showed class with only one func for getting ListView, but Generamba create extension for DIResolver for each new module. I hope it became clearer.
Back to View part. Again, in we have:
- Presenter property
- Router property
- ViewModel property. It @ObservedObject Property wrapper. What is it? It create for us reference to ViewModel which we can change from Presenter and changes showed on View automatically! It looks like magic.
- body calculated property for some piece of our UI. You can check about this in WWDC SwiftUI session
- Extension for ListViewProtocol.
- PreviewProvider part. It is used for show app preview in SwiftUI without simulator
Presenter Part
Presenter contains:
- ObservedObject ViewModel. It’s reference to our ViewModel which used View. Each change in ViewModule in Presenter will change our ViewModel in View and create new UI.
- Interactor property
- View property for some actions which can’t be run by ViewModel
- Extension for ListPresenterProtocol. Every action in View is called this Part, in our app it gets new value from Interactor (used callback) and add to ViewModule array.
Interactor Part
It’s really simple and it does not have any differences with Interactor in UIKit VIPER.
Protocol
It’s simpler and has some differences. It doesn't have Router (or Wireframe) protocol. These are all differences:)
Ultimately, this architecture is very similar to VIPER but it is not VIPER. On the other hand, everyone has their own VIPER. Maybe you will like our implementation of this architecture and you will begin using it in your projects. We will definitely use it because it allows us to realize all the features of SwiftUI and at the same time not to lose the possibility of covering with tests. Thank you for reading this. I hope you enjoyed it.
Our GitHub Template SwiftUI VIPER for Generamba: https://github.com/ESKARIA/vip_model_generamba