Implementation of VIPER architecture for using in iOS platform
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
Templates
VIPERBase.xcodeproj
VIPERBase
.gitignore
LICENSE
README.md Minor fix in readme file. Jan 11, 2019
viper-base.podspec

README.md

VIPERBase

VIPERBase is a implementation of VIPER architecture for using in iOS platform.

This project aims to make VIPER usage and adoption easier, facilitating all needed setting for working with this architecture and trying to reduce as much as possible the manual work arised due to its characteristic.

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. To integrate VIPERBase into your xcode project, specify it in your Podfile:

pod 'viper-base', '~> 2.0'

Xcode template installation

The baddest experience you may have when using VIPER is to create, by yourself, all files, classes and protocols needed by this architecture. This affects a lot the productivity, making a simple task take a lot of time.

Xcode allows us to create personalized templates. With this resource available, we decided to create a template specially for VIPERBase. Using this template, there won't be manual work anymore.

To install and use our template, check this tutorial.

Module overview

A module represents a screen of the app.

This implementation of VIPER, has some particularities:

  • Builder component

Some approaches consider the module creation task as a responsibility of the router. But this violates the Single Responsibility Principle, since it is already responsible for navigation between modules.

To solve this problem, builder was created. It is in charge of creating all components of the module and make the respective connections.

  • Decoupling between modules and entities

This way, entities can be used in one or more modules, since they are simple structures with no business logic.

  • Router owns a reference to view

iOS architecture, the navigation is performed from a UIViewController to another UIViewController. Because of this, router has to own the refecence for the view of the current module, only for navigation purpose, and receive the view of the destination module, from its builder.

Module components

Contracts

The contracts define how the communication between the layers will be made. Consists of 5 contracts:

View Contract

The view class conforms to this protocol. It defines the communication from presenter to view

//MARK: - View Contract

protocol MyModuleViewProtocol: class {

}

View Output Contract

The presenter class conforms to this protocol. It defines the communication from view to presenter

//MARK: - View Output Contract

protocol MyModuleViewOutputProtocol: class {

}

Interactor Contract

The interactor class conforms to this protocol. It defines the communication from presenter to interactor

//MARK: - Interactor Contract

protocol MyModuleInteractorProtocol: class {

}

Interactor Output Contract

The presenter class conforms to this protocol. It defines the communication from interactor to presenter

//MARK: - Interactor Output Contract

protocol MyModuleInteractorOutputProtocol: class {

}

Router Contract

The router class conforms to this protocol. It defines the communication from presenter to router

//MARK: - Router Contract

protocol MyModuleRouterProtocol: class {

}

View

Basic structure of a view:

final class MyModuleView: UIViewController, VIPERView {
    var presenter: MyModuleViewOutputProtocol!
}

//MARK: - MyModuleViewProtocol

extension MyModuleView: MyModuleViewProtocol {

}

You just need to implement the methods defined in the view contract.


Presenter

Basic structure of a presenter:

final class MyModulePresenter: VIPERPresenter {
    weak var view: MyModuleViewProtocol!
    var interactor: MyModuleInteractorProtocol!
    var router: MyModuleRouterProtocol!
}

//MARK: - MyModuleViewOutputProtocol

extension MyModulePresenter: MyModuleViewOutputProtocol {

}

//MARK: - MyModuleInteractorOutputProtocol

extension MyModulePresenter: MyModuleInteractorOutputProtocol {

}

You just need to implement the methods defined in the view output contract and interactor output contract.


Interactor

Basic structure of an interactor:

final class MyModuleInteractor: VIPERInteractor {
    weak var presenter: MyModuleInteractorOutputProtocol!
}

//MARK: - MyModuleInteractorProtocol

extension MyModuleInteractor: MyModuleInteractorProtocol {

}

You just need to implement the methods defined in the interactor contract.


Router

Basic structure of a router

final class MyModuleRouter: VIPERRouter {
    weak var viewController: UIViewController!
}

//MARK: - MyModuleRouterProtocol

extension MyModuleRouter: MyModuleRouterProtocol {

}

You just need to implement the methods defined in the router contract.

Navigation between modules

In the router, navigation can be done in two ways: presenting the module modally or pushing the module onto a navigation stack. To perform navigation, use the methods below:

- presentModule(withView:embedIn:animated:completion:)

This method presents the next module modally. Check parameters details below:

  • withView: View of the module to navigate to.
  • embedIn: .navigationController or .none. The default value is .none
  • animated: Whether or not to perform the animation of the transition. The default value is true
  • completion: Handler called when transition finishes. The default value is nil

- pushModule(withView:embedIn:animated:)

This method pushes the next module onto the navigation stack. It only works if the current module is embeded in a navigation controller or is part of a navigation stack.

  • withView: View of the module to navigate to.
  • embedIn: .navigationController or .none. The default value is .none
  • animated: Whether or not to perform the animation of the transition. The default value is true

Builder

In the builder class, you specify the respective classes for View, Presenter, Interactor and router for that module.

final class MyModuleBuilder: VIPERBuilder {
    typealias View = MyModuleView
    typealias Presenter = MyModulePresenter
    typealias Interactor = MyModuleInteractor
    typealias Router = MyModuleRouter

    static var viewType: VIPERBaseViewType {
        return .storyboard(name: "MyModuleView", bundle: nil)
    }
}

//MARK: - Builder custom methods

extension MyModuleBuilder {

}

You also define the way the view UI will be loaded, through the viewType property. There are 3 possible values:

  • Storyboard file: You just need to inform the name of the storyboard file, without extension, and the bundle, if needed.
.storyboard(name: "MyModuleView", bundle: nil)
  • XIB file: You just need to inform the name of the XIB file, without extension, and the bundle, if needed.
.nib(name: "MyModuleView", bundle: nil)
  • None: If you intent to implement the UI programmatically, use this option.
.none

Building a module

The 3 methods below can be used to build a module. Additionally you can create custom build methods, according to the module needs.

- build():

Creates the module and returns a tuple containig the view and presenter, You can use presenter reference for passing data between the modules.

//MARK: - MyModuleRouterProtocol

extension MyModuleRouter: MyModuleRouterProtocol {
    
    func goToNextModule() {
        let module = NextModuleBuilder.build()
        pushModule(withView: module.view)
    }
}

- buildAndAttachToWindow()

This is a special build method, usually used for starting the initial module of the app, called in AppDelegate class:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        window = InitialModuleBuilder.buildAndAttachToWindow()
        return true
    }
}

- buildAndAttachToNavigationController(tabBarItem:)

This method creates the module, attach it to a navigation controller and returns the navigation controller reference. If you intent to use the module inside a tab bar controller, you can use tabBarItem parameter to configure the tab bar item for this module.

let tabBarController = UITabBarController()

let bookmarksItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 0)
let contactsItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 1)
let downloadsItem = UITabBarItem(tabBarSystemItem: .downloads, tag: 2)

tabBarController.viewControllers = [
    BookmarksBuilder.buildAndAttachToNavigationController(tabBarItem: bookmarksItem),
    ContactsBuilder.buildAndAttachToNavigationController(tabBarItem: contactsItem),
    DownloadsBuilder.buildAndAttachToNavigationController(tabBarItem: downloadsItem)
]

Custom build methods

If you have a specific module that expects to receive some data, it is convenient to create a custom build method for that module. That way, the builder will be in charge of pass this data to the presenter.

To create custom build methods:

  1. Define a static method in the builder class, defining the expected parameters and returning View;
  2. Start the implementation calling the build() method;
  3. Pass the data to the presenter, according to the implementation;
  4. return the view.
//MARK: - Builder custom methods

extension NextModuleBuilder {
    
    static func build(someData: Any, anotherData: Any) -> View {
        let module = build()        
        module.presenter.someData = someData
        module.presenter.anotherData = anotherData
        return module.view
    }
}

Routers can call this builder like this:

let view = NextModuleBuilder.build(someData: "Example of data", anotherData: "Another example of data")
pushModule(withView: view)

License

VIPERBase is released under the MIT license.