• Stars
    star
    576
  • Rank 75,742 (Top 2 %)
  • Language
    HTML
  • License
    Apache License 2.0
  • Created about 7 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Used for generating template files for the VIPER architecture, which solves the common Massive View Controller issues in iOS apps.

iOS VIPER

Versions

Latest version is v4.0.1

If you need to use any older version you can find them:

Installation instructions

To install VIPER Xcode templates clone this repo and run the following command from root folder:

make install_templates

To uninstall Xcode template run:

make uninstall_templates

After that, restart your Xcode if it was already opened.

Demo project

There's a TV Shows demo project in Demo folder. You can find most common VIPER module use cases in it. If you're already familiar with the base Viper modules you can check out our RxModule Guide.

If you want to check out how you could use Formatter in your apps, feel free to check out Formatter Guide.

VIPER short introduction

How to organize all your code and not end up with a couple of Massive View Controllers with millions of lines of code? In short, VIPER (View Interactor Presenter Entity Router) is an architecture which, among other things, aims at solving the common Massive View Controller problem in iOS apps. When implemented to its full extent it achieves complete separation of concerns between modules, which also yields testability. This is good because another problem with Apple's Model View Controller architecture is poor testability.

If you search the web for VIPER architecture in iOS apps you'll find a number of different implementations and a lot of them are not covered in enough detail. At Infinum we have tried out several approaches to this architecture in real-life projects and with that we have defined our own version of VIPER which we will try to cover in detail here.

Let's go over the basics quickly - the main components of VIPER are as follows:

  • View: contains UI logic and knows how to layout and animate itself. It displays what it's told by the Presenter and it delegates user interaction actions to the Presenter. Ideally it contains no business logic, only view logic.
  • Interactor: used for fetching data when requested by the Presenter, regardless of where the data is coming from. Contains only business logic.
  • Presenter: also known as the event handler. Handles all the communication with view, interactor and wireframe. Contains presentation logic - basically it controllers the module.
  • Entity: models which are handled by the Interactor. Contains only business logic, but primarily data, not rules.
  • Formatter(new): handles formatting logic. Sits between presenter and the view. It formats the data from the business world into something that can be consumed by the view.
  • Router: handles navigation logic. In our case we use components called Wireframes for this responsibility.

Components

Your entire app is made up of multiple modules which you organize in logical groups and use one storyboard for that group. In most cases the modules will represent screens and your module groups will represent user-stories, business-flows and so on.

Module components:

  • View
  • Presenter
  • Interactor (not mandatory)
  • Formatter (not mandatory)
  • Wireframe

In some simpler cases you won't need an Interactor for a certain module, which is why this component is not mandatory. These are cases where you don't need to fetch any data, which is usually not common.

Wireframes inherit from the BaseWireframe. Presenters and Interactors do not inherit any class. Views most often inherit UIViewControllers. All protocols should be located in one file called Interfaces. More on this later.

Communication and references

The following pictures shows relationships and communication for one module.

iOS VIPER GRAPH

Let's take a look at the communication logic.

  • LoginViewController communicates with LoginPresenter via a LoginPresenterInterface protocol
  • LoginPresenter communicates with LoginViewController via a LoginViewInterface protocol
  • LoginPresenter communicates with LoginInteractor via a LoginInteractorInterface protocol
  • LoginPresenter communicates with LoginWireframe via a LoginWireframeInterface protocol

The communication between most components of a module is done via protocols to ensure scoping of concerns and testability. Only the Wireframe communicates directly with the Presenter since it actually instantiates the Presenter, Interactor and View and connects the three via dependency injection.

Now let's take a look at the references logic.

  • LoginPresenter has a strong reference to LoginInteractor
  • LoginPresenter has a strong reference to LoginWireframe
  • LoginPresenter has a unowned reference to LoginViewController
  • LoginViewController has a strong reference to LoginPresenter

The reference types might appear a bit counter-intuitive, but they are organized this way to assure all module components are not deallocated from memory as long as one of its Views is active. In this way the Views lifecycle is also the lifecycle of the module - which actually makes perfect sense.

The creation and setup of module components is done in the Wireframe. The creation of a new Wireframe is almost always done in the previous Wireframe. More details on this later in the actual code.

Before we go into detail we should comment one somewhat unusual decision we made naming-wise and that's suffixing protocol names with "Interface" (LoginWireframeInterface, RegisterViewInterface, ...). A common way to do this would be to omit the "Interface" part but we've found that this makes code somewhat less readable and the logic behind VIPER harder to grasp, especially when starting out.

1. Base classes and interfaces

The module generator tool will generate five files - but in order for these to work you will need a couple of base protocols and classes. These are also available in the repo. Let's start by covering these base files: WireframeInterface, BaseWireframe, ViewInterface, InteractorInterface, PresenterInterface, UIStoryboardExtension:

WireframeInterface and BaseWireframe

import UIKit

protocol WireframeInterface: AnyObject {
}

class BaseWireframe<ViewController> where ViewController: UIViewController {

    private weak var _viewController: ViewController?

    // We need it in order to retain the view controller reference upon first access
    private var temporaryStoredViewController: ViewController?

    init(viewController: ViewController) {
        temporaryStoredViewController = viewController
        _viewController = viewController
    }

}

extension BaseWireframe: WireframeInterface {

}

extension BaseWireframe {

    var viewController: ViewController {
        defer { temporaryStoredViewController = nil }
        guard let vc = _viewController else {
            fatalError(
            """
            The `ViewController` instance that the `_viewController` property holds
            was already deallocated in a previous access to the `viewController` computed property.

            If you don't store the `ViewController` instance as a strong reference
            at the call site of the `viewController` computed property,
            there is no guarantee that the `ViewController` instance won't be deallocated since the
            `_viewController` property has a weak reference to the `ViewController` instance.

            For the correct usage of this computed property, make sure to keep a strong reference
            to the `ViewController` instance that it returns.
            """
            )
        }
        return vc
    }

    var navigationController: UINavigationController? {
        return viewController.navigationController
    }

}

extension UIViewController {

    func presentWireframe<ViewController>(_ wireframe: BaseWireframe<ViewController>, animated: Bool = true, completion: (() -> Void)? = nil) {
        present(wireframe.viewController, animated: animated, completion: completion)
    }

}

extension UINavigationController {

    func pushWireframe<ViewController>(_ wireframe: BaseWireframe<ViewController>, animated: Bool = true) {
        pushViewController(wireframe.viewController, animated: animated)
    }

    func setRootWireframe<ViewController>(_ wireframe: BaseWireframe<ViewController>, animated: Bool = true) {
        setViewControllers([wireframe.viewController], animated: animated)
    }

}

The Wireframe is used in 2 steps:

  1. Initialization using a UIViewController (see the init method). Since the Wireframe is in charge of performing the navigation it needs access to the actual UIViewController with which it will do so.
  2. Navigation to a screen (see the pushWireframe, presentWireframe and setRootWireframe methods). Those methods are defined on UIViewController and UINavigationController since those objects are responsible for performing the navigation.

ViewInterface, InteractorInterface and PresenterInterface

protocol ViewInterface: AnyObject {
}

extension ViewInterface {
}
protocol InteractorInterface: AnyObject {
}

extension InteractorInterface {
}
protocol PresenterInterface: AnyObject {
}

extension PresenterInterface {
}

These interfaces are initially empty. They exists just to make it simple to insert any and all functions needed in all views/interactors/presenters in you project. ViewInterface and InteractorInterface protocols need to be class bound because the Presenter will hold them via a weak reference.

Ok, let's get to the actual module. First we'll cover the files you get when creating a new module via the module generator.

2. What you get when generating a module

When running the module generator you will get five files. Say we wanted to create a Home module, we would get the following: HomeInterfaces, HomeWireframe, HomePresenter, HomeView and HomeInteractor. Let's go over all five.

Interfaces

protocol HomeWireframeInterface: WireframeInterface {
}

protocol HomeViewInterface: ViewInterface {
}

protocol HomePresenterInterface: PresenterInterface {
}

protocol HomeInteractorInterface: InteractorInterface {
}

This interface file will provide you with a nice overview of your entire module at one place. Since most components communicate with each other via protocols we found very useful to put all of these protocols for one module in one place. That way you have a very clean overview of the entire behavior of the module.

Wireframe

final class HomeWireframe: BaseWireframe<HomeViewController> {

    // MARK: - Private properties -

    private let storyboard = UIStoryboard(name: "Home", bundle: nil)

    // MARK: - Module setup -

    init() {
        let moduleViewController = storyboard.instantiateViewController(ofType: HomeViewController.self)
        super.init(viewController: moduleViewController)

        let interactor = HomeInteractor()
        let presenter = HomePresenter(view: moduleViewController, interactor: interactor, wireframe: self)
        moduleViewController.presenter = presenter
    }

}

// MARK: - Extensions -

extension HomeWireframe: HomeWireframeInterface {
}

It generates a Storyboard file for you too so you don't have to create one yourself. You can tailor the Storyboard to match its purpose.

Presenter

final class HomePresenter {

    // MARK: - Private properties -

    private unowned let view: HomeViewInterface
    private let interactor: HomeInteractorInterface
    private let wireframe: HomeWireframeInterface

    // MARK: - Lifecycle -

    init(
        view: HomeViewInterface,
        interactor: HomeInteractorInterface,
        wireframe: HomeWireframeInterface
    ) {
        self.view = view
        self.interactor = interactor
        self.wireframe = wireframe
    }
}

// MARK: - Extensions -

extension HomePresenter: HomePresenterInterface {
}

This is the skeleton of a Presenter which will get a lot more meat on it once you start implementing the business logic.

View

final class HomeViewController: UIViewController {

    // MARK: - Public properties -

    var presenter: HomePresenterInterface!

    // MARK: - Life cycle -

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

// MARK: - Extensions -

extension HomeViewController: HomeViewInterface {
}

Like the Presenter above, this is only a skeleton which you will populate with IBOutlets, animations and so on.

Interactor

final class HomeInteractor {
}

extension HomeInteractor: HomeInteractorInterface {
}

When generated your Interactor is also a skeleton which you will in most cases use to perform fetching of data from remote API services, Database services, etc.

3. How it really works

Here's an example of a wireframe for a Home screen. Let's start with the Presenter.

final class HomePresenter {

    // MARK: - Private properties -

    private unowned let view: HomeViewInterface
    private let interactor: HomeInteractorInterface
    private let wireframe: HomeWireframeInterface

    private var items: [Show] = [] {
        didSet {
            view.reloadData()
        }
    }

    // MARK: - Lifecycle -

    init(
        view: HomeViewInterface,
        interactor: HomeInteractorInterface,
        wireframe: HomeWireframeInterface
    ) {
        self.view = view
        self.interactor = interactor
        self.wireframe = wireframe
    }
}

// MARK: - Extensions -

extension HomePresenter: HomePresenterInterface {
    func logout() {
        interactor.logout()
        wireframe.navigateToLogin()
    }

    var numberOfItems: Int {
        items.count
    }

    func item(at indexPath: IndexPath) -> Show {
        items[indexPath.row]
    }

    func itemSelected(at indexPath: IndexPath) {
        let show = items[indexPath.row]
        wireframe.navigateToShowDetails(id: show.id)
    }

    func loadShows() {
        view.showProgressHUD()
        interactor.getShows { [unowned self] result in
            switch result {
            case .failure(let error):
                showValidationError(error)
            case .success(let shows):
                items = shows
            }
            view.hideProgressHUD()
        }
    }

}

private extension HomePresenter {
    func showValidationError(_ error: Error) {
        wireframe.showAlert(with: "Error", message: error.localizedDescription)
    }
}

In this simple example the Presenter fetches TV shows by doing an API call and handles the result. The Presenter can also handle the logout action and item selection in a tableView which is delegated from the view. If an item has been selected the Presenter will initiate opening of the Details screen.

final class HomeWireframe: BaseWireframe<HomeViewController> {

    // MARK: - Private properties -

    private let storyboard = UIStoryboard(name: "Home", bundle: nil)

    // MARK: - Module setup -

    init() {
        let moduleViewController = storyboard.instantiateViewController(ofType: HomeViewController.self)
        super.init(viewController: moduleViewController)

        let interactor = HomeInteractor()
        let presenter = HomePresenter(view: moduleViewController, interactor: interactor, wireframe: self)
        moduleViewController.presenter = presenter
    }

}

// MARK: - Extensions -

extension HomeWireframe: HomeWireframeInterface {
    func navigateToLogin() {
        navigationController?.setRootWireframe(LoginWireframe())
    }

    func navigateToShowDetails(id: String) {
        navigationController?.pushWireframe(DetailsWireframe())
    }

}

This is also a simple example of a wireframe which handles two navigation functions. You've maybe noticed the showAlert Wireframe method used in the Presenter to display alerts. This is used in the BaseWireframe in this concrete project and looks like this:

func showAlert(with title: String?, message: String?) {
    let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
    showAlert(with: title, message: message, actions: [okAction])
}

This is just one example of some shared logic you'll want to put in your base class or maybe one of the base protocols.

Here's an example of a simple Interactor we used in the Demo project:

final class HomeInteractor {
    private let userService: UserService
    private let showService: ShowService

    init(userService: UserService = .shared, showService: ShowService = .shared) {
        self.userService = userService
        self.showService = showService
    }
}

// MARK: - Extensions -

extension HomeInteractor: HomeInteractorInterface {
    func getShows(_ completion: @escaping ((Result<[Show], Error>) -> ())) {
        showService.getShows(completion)
    }

    func logout() {
        userService.removeUser()
    }
}

The Interactor contains services which actually communicate with the server. The Interactor can contain as many services as needed but beware that you don't add the ones which aren't needed.

How it's organized in Xcode

Using this architecture impacted the way we organize our projects. In most cases we have four main subfolders in the project folder: Application, Common, Modules and Resources. Let's go over those a bit.

Application

Contains AppDelegate and any other app-wide components, initializers, appearance classes, managers and so on. Usually this folder contains only a few files.

Common

Used for all common utility and view components grouped in sub folders. Some common cases for these groups are Analytics, Constants, Extensions, Protocols, Views, Networking, etc. Also here is where we always have a VIPER subfolder which contains the base VIPER protocols and classes.

Resources

This folder should contain image assets, fonts, audio and video files, and so on. We use one .xcassets for images and in that folder separate images into logical folders so we don't get a long list of files in one place.

Modules

As described earlier you can think of one VIPER module as one screen. In the Modules folder we organize screens into logical groups which are basically user-stories. Each group is organized in a sub-folder which contains one storyboard (containing all screens for that group) and multiple module sub-folders.

iOS VIPER MODULES

Useful links

Contributing and development

Feedback and code contributions are very much welcome. Just make a pull request with a short description of your changes.

Before creating a PR, please run:

ruby main.rb

from Templates directory to generate all templates files.

By making contributions to this project you give permission for your code to be used under the same license.

Credits

Maintained and sponsored by Infinum.

More Repositories

1

android_dbinspector

Android library for viewing, editing and sharing in app databases.
Kotlin
945
star
2

rails-handbook

Describing the development process used by the Infinum Rails Team.
Slim
763
star
3

FBAnnotationClustering

iOS library for clustering map notifications in an easy and performant way
Objective-C
713
star
4

Android-Goldfinger

Android library to simplify Biometric authentication implementation.
Java
652
star
5

phrasing

Edit phrases inline for your Ruby on Rails applications!
Ruby
544
star
6

eightshift-boilerplate

This repository contains all the tools you need to start building a modern WordPress theme, using all the latest front end development tools.
PHP
511
star
7

Android-GoldenEye

A wrapper for Camera1 and Camera2 API which exposes simple to use interface.
Kotlin
375
star
8

cookies_eu

Gem to add cookie consent to Rails application
Haml
266
star
9

dox

Automated API documentation from Rspec
Ruby
234
star
10

MjolnirRecyclerView

[DEPRECATED] This library is no longer maintained and it will not receive any more updates.
Java
220
star
11

ios-nuts-and-bolts

iOS bits and pieces that you can include in your project to make your life a bit easier.
Swift
195
star
12

Japx

Lightweight parser for the complex JSON:API (http://jsonapi.org/) structure.
Swift
150
star
13

flutter-charts

Customizable charts library for flutter.
Dart
139
star
14

datx

DatX is an opinionated JS/TS data store. It features support for simple property definition, references to other models and first-class TypeScript support.
TypeScript
137
star
15

learnQuery

Learn JavaScript fundamentals by building your own jQuery equivalent library
JavaScript
137
star
16

flutter-dasher

Dart
116
star
17

floggy

Customizable logger for dart and flutter applications.
Dart
115
star
18

android-complexify

An Android library which makes checking the quality of user's password a breeze.
Java
113
star
19

Android-Prince-of-Versions

Android library for handling application updates.
Java
104
star
20

android_connectionbuddy

Utility library for handling connectivity change events.
Java
94
star
21

eightshift-docs

A documentation website for Eightshift open source projects
JavaScript
79
star
22

eightshift-frontend-libs

Frontend library that exposes custom scripts and styles for modern WordPress projects
JavaScript
69
star
23

Retromock

Java library for mocking responses in a Retrofit service.
Kotlin
67
star
24

eightshift-libs

Library that is meant to be used inside Eightshift Boilerplate and Eightshift Boilerplate Plugin libs via composer in order to be able to easily set up a modern development process.
PHP
61
star
25

frontend-handbook

Our handbook based on 10 years of experience in Frontend/JS development
Slim
57
star
26

Locker

Securely lock your secrets under the watch of TouchID or FaceID keeper πŸ”’
Swift
50
star
27

emotion-normalize

normalize.css but for emotion.js
JavaScript
50
star
28

mobx-jsonapi-store

JSON API Store for MobX
TypeScript
48
star
29

Dagger-2-Example

Dagger 2 example project
Java
44
star
30

ios-prince-of-versions

Library used for easier versioning of your applications, allowing you to prompt your users to update the app to the newest version
Swift
43
star
31

enumerations

Better Rails Enumerations
Ruby
37
star
32

kotlin-jsonapix

JsonApiX is an Android, annotation processor library that was made to transform regular Kotlin classes into their JSON API representations, with the ability to serialize or deserialize them to or from strings.
Kotlin
36
star
33

eightshift-boilerplate-plugin

This repository contains all the tools you need to start building a modern WordPress plugin.
PHP
36
star
34

mobx-collection-store

Data collection store for MobX
TypeScript
35
star
35

android-sentinel

Sentinel is a simple one screen UI which provides a standardised entry point for tools used in development and QA alongside device, application and permissions data.
Kotlin
35
star
36

decoupled-json-content

JavaScript
30
star
37

wordpress-handbook

Official WordPress handbook at Infinum
Slim
30
star
38

webpack-asset-pipeline

πŸš€ A missing link for the asset pipeline alternative with Webpack.
JavaScript
30
star
39

ios-loggie

Simplify debugging by showing network requests of your app as they happen.
Swift
30
star
40

flutter-plugins-locker

Flutter plugin that secures your secrets in keychain using biometric authentication (Fingerprint, Touch ID, Face ID...).
Dart
29
star
41

default_rails_template

Default template for generating new Rails applications.
Ruby
27
star
42

eightshift-forms

WordPress plugin project for Gutenberg forms
PHP
27
star
43

media-blender

Easy and predictable SASS/SCSS media queries
CSS
26
star
44

android-collar

Gradle plugin which collects all analytics screen names, events and user properties for Android projects.
Kotlin
26
star
45

secrets_cli

CLI for storing and reading your secrets via vault
Ruby
25
star
46

flutter-plugins-japx

JSON API parser for Flutter
Dart
24
star
47

flutter-bits

Flutter
Dart
22
star
48

react-mobx-translatable

Make React components translatable using MobX
JavaScript
21
star
49

dungeons-and-dragons

🎲 Dungeons & Dragons Character builder and keeper (work in progress)
TypeScript
19
star
50

android-crash-handler

Utility library which handles crash handler configuration
Java
19
star
51

qa-handbook

Describing the processes used by the Infinum QA Team
Slim
18
star
52

iOS-Bugsnatch

Swift
18
star
53

eightshift-coding-standards

Eightshift coding standards for WordPress
PHP
16
star
54

jsonapi-query_builder

Ruby
14
star
55

thrifty-retrofit-converter

Retrofit converter which uses Thrifty for Apache Thrift-compatible serialization
Java
13
star
56

json-wp-post-parser

JSON Post Parser plugin parses your content and saves it as JSON available in REST posts and pages endpoints.
PHP
13
star
57

swift-style-guide

12
star
58

generator-infinitely-static

πŸ’« Static page generator with routes support thats infinitely awesome
JavaScript
11
star
59

react-responsive-ssr

TypeScript
11
star
60

JS-React-Example

Infinum's way of doing React
TypeScript
10
star
61

ngx-hal

Angular datastore library with HAL support
TypeScript
10
star
62

fiscalizer

A gem for fiscalizing invoices in Croatia using Ruby.
Ruby
9
star
63

ember-form-object

Form object pattern in ember apps
JavaScript
9
star
64

js-talks

✨ Interesting talks and mini lectures about new and cool stuff that's going on in the world of JS development, organized by the Infinum JS team
9
star
65

android-handbook

Slim
8
star
66

js-linters

Infinum's JS team linter rules
TypeScript
7
star
67

auth-worker

OAuth2 Service Worker handler
TypeScript
7
star
68

array_validator

Array Validations for ActiveModel
Ruby
7
star
69

eightshift-storybook

Storybook package for Eightshift-Boilerplate
7
star
70

icomcom-react

πŸ’¬ A React component for handling communication with content in <iframe />
JavaScript
7
star
71

dox-demo

Demo app for dox gem:
HTML
6
star
72

android-localian

Android library that manages your application locale and language across multiple Android API levels.
Kotlin
6
star
73

ios-handbook

Slim
6
star
74

flutter-prince-of-versions

Library used for easier versioning of your applications, allowing you to prompt your users to update the app to the newest version
Dart
6
star
75

learn-react

πŸ‘‹ βš›οΈ Learn React by implementing your own!
TypeScript
5
star
76

I18n-js

Javascript library for string internationalization.
CoffeeScript
5
star
77

ios-collar

In-app analytics debugging tool
Swift
5
star
78

ngx-form-object

Reactive forms manager
TypeScript
5
star
79

ios-sentinel

Developer’s toolbox for debugging applications
Swift
4
star
80

mobx-keys-store

Keys store for MobX
TypeScript
4
star
81

mysterious-sampler

Swift
4
star
82

rails-infinum-jsonapi_example_app_old

Ruby
4
star
83

phrasing_plus

Phrasing extension for editing images inline
Ruby
4
star
84

eightshift-web-components

Web components library that exposes custom scripts and styles for modern WordPress projects
Svelte
4
star
85

Install-Flutter-Version-Manager-Bitrise

Shell
4
star
86

infinum_setup

Setup script
Ruby
4
star
87

loglevel-filesave

Loglevel plugin for saving logs to the file
JavaScript
4
star
88

eightshift-blocks

Project dedicated to use inside WP Boilerplate and WP Boilerplate Plugin projects via composer to be able to easily setup modern development process for Gutenberg blocks..
PHP
3
star
89

data_sync

Rails plugin for database and file synchronization
Ruby
3
star
90

next-passenger

next.js with passenger proof of concept
Dockerfile
3
star
91

SocketMan

Android WebSocket client app
Java
3
star
92

react-asset-collector

Collect assets from react components so you can do HTTP2 push
JavaScript
3
star
93

rails_log_book

Ruby
3
star
94

mina-secrets

Mina plugin for secrets_cli gem
Ruby
2
star
95

docusaurus-theme

Infinum Docusaurus teme
JavaScript
2
star
96

ngx-nuts-and-bolts

A collection of commonly used pieces of Angular-related code that we use everyday at Infinum.
TypeScript
2
star
97

money_with_date

Extension for the money gem which adds dates to Money objects.
Ruby
2
star
98

blog-android-permissions

Java
2
star
99

JS-RxWorkshop

TypeScript
2
star
100

analytics-handbook

2
star