On this tutorial I will present you how you can mix SwiftUI with the VIPER structure in an actual world iOS utility instance.
VIPER
SwiftUI – the brand new child on the block
There are actually a whole bunch of SwiftUI tutorials across the internet, however I used to be solely capable of finding only one or two that focuses on actual world use circumstances as an alternative of the smaller particulars like how you can configure / make X in SwiftUI. Good tutorials @mecid stick with it!
I additionally had my very own “wrestle” with SwiftUI, as a result of my assortment view framework is structured precisely the identical approach as you write SwiftUI code. After WWDC I used to be like, hell no! I am doing the identical methodology for months now, so why ought to I care? I began to consider that some Apple engineers are studying my weblog. 😂
Anyway I knew at day zero {that a} loopy quantity of recent SwiftUI tutorials will arrive and everybody might be hyped in regards to the new declarative UI framework, however actually I already had my common toolkit for this objective. That is why I do not wished to put in writing about it. Actually I nonetheless love Mix way more than SwiftUI. I am additionally fairly dissatisfied since CollectionView is totally lacking from the framework.
Lastly, simply because what the heck lets attempt new issues and I used to be inquisitive about how SwiftUI can match into my app constructing methodology I began to create a brand new VIPER template based mostly on these type of views. I additionally wished to make a helpful, scalable, modular actual world utility instance utilizing the brand new framework, which is up-to-date. Lots has modified in SwiftUI through the Xcode 11 beta interval, in order that’s why I am solely publishing this tutorial now. Sufficient chatter, should not we code already? 😛
Study a contemporary VIPER structure
I’ve spent my final two years utilizing the VIPER structure. Some individuals say “it is approach too advanced” or “it is not match for small groups”. I can solely inform them one phrase:
Bullshit!
I consider that I’ve created a contemporary & comparatively easy sample that can be utilized for actually something. Studying VIPER will certainly enhance your code high quality because of the clear structure and the SOLID ideas. You may have a greater understanding of how smaller items can work collectively and talk with one another.
Remoted smaller elements can pace up growth, since you simply must work on just a little piece directly, plus you may create assessments for that specific factor, which is a big win for testability & code protection (you do not have to run your app on a regular basis if you wish to take a look at one thing, you may work on the module you simply want).
I am often working with a extremely easy code generator to fireside up new modules, this manner I can save a whole lot of time. If it’s important to work alone on a challenge the module generator and the predefined construction may even prevent some extra time. Additionally you actually cannot mess up issues or find yourself with huge recordsdata if you’re following the fundamental VIPER guidelines. I will educate you my methodology in about 10 minutes. ⏰
What the heck is VIPER anyway?
For those who by no means heard about VIPER earlier than, the very first thing it’s best to know is {that a} VIPER module incorporates the next elements:
- View = UIViewController subclass or SwiftUI View
- Interactor = Supplies the required information within the correct format
- Presenter = UI impartial enterprise logic (what to do precisely)
- Entity = Information objects (typically it is lacking from the module)
- Router = Builds up the view controller hierarchy (present, current, dismiss, and so forth)
I at all times have a module file subsequent to those ones the place I outline a module builder which builds up the entire thing from the elements above and in that file I additionally outline the module particular protocols. I often title these protocols as interfaces they make it doable that any of the elements could be changed utilizing dependency injection. This manner we are able to take a look at something through the use of mocked objects in our unit assessments.
Some say {that a} VIPER module with a Builder known as VIPER/B. I believe the module file is a perfect place to retailer your module builder object, the module interfaces and the module delegate if you happen to want one.
Protocol oriented VIPER structure
So the bottom line is the 6 foremost protocol that connects View-Interactor-Presenter-Router. These protocols make sure that not one of the VIPER elements can see greater than it is required. For those who return to my first tutorial you may see that I made a mistake there. The factor is that you would be able to name a way on the router by the presenter from the view, which is dangerous. This new strategy fixes that situation. 🐛
View-to-Presenter
Presenter-to-View
Router-to-Presenter
Presenter-to-Router
Interactor-to-Presenter
Presenter-to-Interactor
Module
builds up pointers and returns a UIViewController
View implements View-to-Presenter
robust presenter as Presenter-to-View-interface
Presenter implements Presenter-to-Router, Presenter-to-Interactor, Presenter-to-View
robust router as Router-to-Presenter-interface
robust interactor as Interactor-to-Presenter-interface
weak view as View-to-Presenter-interface
Interactor implements Interactor-to-Presenter
weak presenter as Presenter-to-Interactor-interface
Router implemenents Presenter-to-Router
weak presenter as Presenter-to-Router-interface
As you may see the view (which is usually aUIViewController
subclass) holds the presenter strongly and the presenter will retain the interactor and router courses. Every part else is a weak pointer, as a result of we do not like retain cycles. It’d appears just a little bit difficult at first sight, however after writing your first few modules you may see how good is to separate logical elements from one another. 🐍
Please observe that not all the things is a VIPER module. Do not attempt to write your API communication layer or a CoreLocation service as a module, as a result of these type of stuff are standalone for example: providers. I will write about them within the subsequent one, however for now let’s simply give attention to the anatomy of a VIPER module.
Generic VIPER implementation in Swift 5
Are you prepared to put in writing some Swift code? All proper, let’s create some generic VIPER interfaces that may be prolonged afterward, do not be afraid will not be that tough. 😉
public protocol RouterPresenterInterface: class {
}
public protocol InteractorPresenterInterface: class {
}
public protocol PresenterRouterInterface: class {
}
public protocol PresenterInteractorInterface: class {
}
public protocol PresenterViewInterface: class {
}
public protocol ViewPresenterInterface: class {
}
public protocol RouterInterface: RouterPresenterInterface {
associatedtype PresenterRouter
var presenter: PresenterRouter! { get set }
}
public protocol InteractorInterface: InteractorPresenterInterface {
associatedtype PresenterInteractor
var presenter: PresenterInteractor! { get set }
}
public protocol PresenterInterface: PresenterRouterInterface & PresenterInteractorInterface & PresenterViewInterface {
associatedtype RouterPresenter
associatedtype InteractorPresenter
associatedtype ViewPresenter
var router: RouterPresenter! { get set }
var interactor: InteractorPresenter! { get set }
var view: ViewPresenter! { get set }
}
public protocol ViewInterface: ViewPresenterInterface {
associatedtype PresenterView
var presenter: PresenterView! { get set }
}
public protocol EntityInterface {
}
public protocol ModuleInterface {
associatedtype View the place View: ViewInterface
associatedtype Presenter the place Presenter: PresenterInterface
associatedtype Router the place Router: RouterInterface
associatedtype Interactor the place Interactor: InteractorInterface
func assemble(view: View, presenter: Presenter, router: Router, interactor: Interactor)
}
public extension ModuleInterface {
func assemble(view: View, presenter: Presenter, router: Router, interactor: Interactor) {
view.presenter = (presenter as! Self.View.PresenterView)
presenter.view = (view as! Self.Presenter.ViewPresenter)
presenter.interactor = (interactor as! Self.Presenter.InteractorPresenter)
presenter.router = (router as! Self.Presenter.RouterPresenter)
interactor.presenter = (presenter as! Self.Interactor.PresenterInteractor)
router.presenter = (presenter as! Self.Router.PresenterRouter)
}
}
Related varieties are simply placeholders for particular varieties, through the use of a generic interface design I can assemble my modules with a generic module interface extension and if some protocol is lacking the app will crash simply as I attempt to initialize the dangerous module.
I like this strategy, as a result of it saves me from a whole lot of boilerplate module builder code. Additionally this manner all the things can have a base protocol, so I can prolong something in a extremely neat protocol oriented approach. Anyway if you happen to do not perceive generics that is not an enormous deal, within the precise module implementation you’ll barely meet them.
So how does an precise module seems to be like?
protocol TodoRouterPresenterInterface: RouterPresenterInterface {
}
protocol TodoPresenterRouterInterface: PresenterRouterInterface {
}
protocol TodoPresenterInteractorInterface: PresenterInteractorInterface {
}
protocol TodoPresenterViewInterface: PresenterViewInterface {
}
protocol TodoInteractorPresenterInterface: InteractorPresenterInterface {
}
protocol TodoViewPresenterInterface: ViewPresenterInterface {
}
remaining class TodoModule: ModuleInterface {
typealias View = TodoView
typealias Presenter = TodoPresenter
typealias Router = TodoRouter
typealias Interactor = TodoInteractor
func construct() -> UIViewController {
let view = View()
let interactor = Interactor()
let presenter = Presenter()
let router = Router()
self.assemble(view: view, presenter: presenter, router: router, interactor: interactor)
router.viewController = view
return view
}
}
remaining class TodoPresenter: PresenterInterface {
var router: TodoRouterPresenterInterface!
var interactor: TodoInteractorPresenterInterface!
weak var view: TodoViewPresenterInterface!
}
extension TodoPresenter: TodoPresenterRouterInterface {
}
extension TodoPresenter: TodoPresenterInteractorInterface {
}
extension TodoPresenter: TodoPresenterViewInterface {
}
remaining class TodoInteractor: InteractorInterface {
weak var presenter: TodoPresenterInteractorInterface!
}
extension TodoInteractor: TodoInteractorPresenterInterface {
}
remaining class TodoRouter: RouterInterface {
weak var presenter: TodoPresenterRouterInterface!
weak var viewController: UIViewController?
}
extension TodoRouter: TodoRouterPresenterInterface {
}
remaining class TodoView: UIViewController, ViewInterface {
var presenter: TodoPresenterViewInterface!
}
extension TodoView: TodoViewPresenterInterface {
}
A VIPER module is created from 5 recordsdata, which is a big enchancment in comparison with my outdated methodology (I used 9 recordsdata for a single module, which remains to be higher than a 2000 strains of code huge view controller, however yeah it was fairly many recordsdata… 😂 ).
You need to use my VIPER protocol library if you would like or just copy & paste these interfaces to your challenge. I even have a VIPER module generator written solely in Swift that may generate a module based mostly on this template (or you may make your personal).
Find out how to construct VIPER interfaces?
Let me clarify a pattern circulate actual fast, take into account the next instance:
protocol TodoRouterPresenterInterface: RouterPresenterInterface {
func dismiss()
}
protocol TodoPresenterRouterInterface: PresenterRouterInterface {
}
protocol TodoPresenterInteractorInterface: PresenterInteractorInterface {
func didLoadWelcomeText(_ textual content: String)
}
protocol TodoPresenterViewInterface: PresenterViewInterface {
func prepared()
func shut()
}
protocol TodoInteractorPresenterInterface: InteractorPresenterInterface {
func startLoadingWelcomeText()
}
protocol TodoViewPresenterInterface: ViewPresenterInterface {
func setLoadingIndicator(seen: Bool)
func setWelcomeText(_ textual content: String)
}
The view calls prepared()
on the presenter sooner or later in time viewDidLoad()
, so the presenter can kick off. First it tells the view to point out the loading indicator by calling setLoadingIndicator(seen: true)
, subsequent asks the interactor to load the welcome textual content asynchronously startLoadingWelcomeText()
. After the info arrives again to the interactor it may well notify the presenter through the use of the didLoadWelcomeText("")
methodology. The presenter can now inform the view to cover the loading indicator utilizing the identical methodology setLoadingIndicator(seen: false)
this time with a false
parameter and to show the welcome textual content through the use of setWelcomeText("")
.
One other use case is that somebody faucets a button on the view so as to shut the controller. The view calls shut()
on the presenter, and the presenter can merely name dismiss()
on the router. The presenter may also do another stuff (like cleansing up some assets) earlier than it asks the router to dismiss the view controller.
I hope that you just get the instance, really feel charge to implement all the things by your personal, it is fairly a pleasant process to apply. After all you may make the most of blocks, guarantees or the model new Mix framework to make your stay simpler. You may for instance auto-notify the presenter if some async information loading have completed. 😉
So now that you’ve a primary understanding a few trendy VIPER structure lets discuss how you can exchange the normal ViewController subclass with SwiftUI.
Find out how to design a VIPER based mostly SwiftUI utility?
SwiftUI is kind of a novel beast. View are structs so our generic VIPER protocol wants some alterations so as to make all the things work.
The very first thing it’s important to do is to eliminate the ViewPresenterInterface
protocol. Subsequent you may take away the view property from the PresenterInterface
since we’ll use an observable view-model sample to auto-update the view with information. The final modification is that it’s important to take away the view
parameter from the default implementation of the assemble operate contained in the ModuleInterface
extension.
So I discussed a view-model, let’s make one. For the sake of simplicity I’ll use an error Bool to point if one thing went unsuitable, however you can use one other view, or a standalone VIPER module that presents an alert message.
import Mix
import SwiftUI
remaining class TodoViewModel: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
@Revealed var error: Bool = false {
willSet {
self.objectWillChange.ship()
}
}
@Revealed var todos: [TodoEntity] = [] {
willSet {
self.objectWillChange.ship()
}
}
}
This class conforms to the ObservableObject
which makes SwiftUI doable to verify for updates & re-render the view hierarchy if one thing modified. You simply want a property with the ObservableObjectPublisher
sort and actually ship()
a message if one thing will change this set off the auto-update in your views. 🔥
The TodoEntity
is only a primary struct that conforms to a bunch of protocols like the brand new Identifiable
from SwiftUI, as a result of we might wish to show entities in an inventory.
import Basis
import SwiftUI
struct TodoEntity: EntityInterface, Codable, Identifiable {
let id: Int
let title: String
let accomplished: Bool
}
A primary SwiftUI view will nonetheless implement the ViewInterface
and it will have a reference to the presenter. Our view-model property can also be going for use right here marked with an @ObservedObject
property wrapper. That is the way it seems to be like in code thus far:
import SwiftUI
struct TodoView: ViewInterface, View {
var presenter: TodoPresenterViewInterface!
@ObservedObject var viewModel: TodoViewModel
var physique: some View {
Textual content("SwiftUI ❤️ VIPER")
}
}
The presenter may also have a weak var viewModel: TodoViewModel!
reference to have the ability to replace the the view-model. Looks as if we have now a two-way communication circulate between the view and the presenter through the use of a view-model. Appears to be like good to me. 👍
We will additionally make the most of the model new @EnvironmentObject
if we wish to move round some information within the view hierarchy. You simply must implement the identical remark protocol in your setting object that we did for the view-model. For instance:
import Basis
import Mix
remaining class TodoEnvironment: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
@Revealed var title: String = "Todo record" {
willSet {
self.objectWillChange.ship()
}
}
}
Lastly let me present you how you can implement the module builder, as a result of that is fairly difficult. It’s a must to use the brand new generic UIHostingController
, which is fortunately an UIViewController
subclass so you may return it after you end module constructing.
remaining class TodoModule: ModuleInterface {
typealias View = TodoView
typealias Presenter = TodoPresenter
typealias Router = TodoRouter
typealias Interactor = TodoInteractor
func construct() -> UIViewController {
let presenter = Presenter()
let interactor = Interactor()
let router = Router()
let viewModel = TodoViewModel()
let view = View(presenter: presenter, viewModel: viewModel)
.environmentObject(TodoEnvironment())
presenter.viewModel = viewModel
self.assemble(presenter: presenter, router: router, interactor: interactor)
let viewController = UIHostingController(rootView: view)
router.viewController = viewController
return viewController
}
}
Placing collectively the items from now’s only a piece of cake. If you’d like, you may problem your self to construct one thing with out downloading the remaining challenge. 🍰
Nicely, if you happen to’re not into challenges that is fantastic too, be happy to seize the instance code from The.Swift.Dev tutorials on GitHub. It incorporates a pleasant interactor with some cool networking stuff utilizing URLSession and the Mix framework. The ultimate SwiftUI code is only a tough implementation, as a result of as I instructed you at first there actually good tutorials about SwiftUI with examples.
I hope you loved this tutorial, please subscribe & inform me what you suppose. 🤔