VIPER Design Pattern

The VIPER design pattern is a powerful architectural pattern used in iOS development to create highly modular and testable code.

article

What is VIPER?


The VIPER design pattern is a powerful architectural pattern used in iOS development to create highly modular and testable code. VIPER stands for View, Interactor, Presenter, Entity, and Router. Each component has a specific responsibility, making the code easier to manage and scale. In this article, we'll explore what VIPER is, why it's beneficial, and how to implement it in your iOS projects.


  • View: The user interface of the application. It displays the data and handles user interactions.

  • Interactor: Contains the business logic of the application. It processes the data and communicates with the Presenter.

  • Presenter: Acts as an intermediary between the View and the Interactor. It receives input from the View, processes it via the Interactor, and updates the View.

  • Entity: Represents the data model. It contains the definitions of the business objects used in the application.

  • Router: Manages the navigation logic. It handles the routing between different screens in the application.


Why Use VIPER?


VIPER offers several benefits for iOS development:


  • **Separation of Concerns:** VIPER separates the application's concerns, making it easier to manage and scale.

  • **Testability:** By dividing the application into distinct components, each part can be tested independently, leading to more reliable code.

  • **Modularity:** VIPER promotes modularity, allowing developers to work on different parts of the application simultaneously without conflicts.


Implementing VIPER in iOS


Let's walk through a simple example of implementing VIPER in an iOS application. We'll create a basic app that displays a list of tasks.


Entity


The Entity contains the data model. Here’s a simple model for a Task:



struct Task {
    let title: String
    let isCompleted: Bool
}

Interactor


The Interactor contains the business logic. It fetches the tasks and communicates with the Presenter:



protocol TaskInteractorInput {
    func fetchTasks()
}

protocol TaskInteractorOutput: AnyObject {
    func didFetchTasks(_ tasks: [Task])
}

class TaskInteractor: TaskInteractorInput {
    weak var output: TaskInteractorOutput?
    
    func fetchTasks() {
        let tasks = [
            Task(title: "Task 1", isCompleted: false),
            Task(title: "Task 2", isCompleted: true)
        ]
        output?.didFetchTasks(tasks)
    }
}

Presenter


The Presenter acts as an intermediary between the View and the Interactor. It processes data and updates the View:



protocol TaskPresenterInput {
    func viewDidLoad()
}

protocol TaskPresenterOutput: AnyObject {
    func displayTasks(_ tasks: [Task])
}

class TaskPresenter: TaskPresenterInput {
    weak var view: TaskPresenterOutput?
    var interactor: TaskInteractorInput?
    
    func viewDidLoad() {
        interactor?.fetchTasks()
    }
}

extension TaskPresenter: TaskInteractorOutput {
    func didFetchTasks(_ tasks: [Task]) {
        view?.displayTasks(tasks)
    }
}

View


The View displays data to the user and handles user interactions:



class TaskViewController: UIViewController, TaskPresenterOutput {
    @IBOutlet weak var tableView: UITableView!
    
    var presenter: TaskPresenterInput?
    var tasks: [Task] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter?.viewDidLoad()
    }
    
    func displayTasks(_ tasks: [Task]) {
        self.tasks = tasks
        tableView.reloadData()
    }
}

extension TaskViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath)
        let task = tasks[indexPath.row]
        cell.textLabel?.text = task.title
        return cell
    }
}

Router


The Router manages the navigation logic of the application:



class TaskRouter {
    static func createModule() -> UIViewController {
        let viewController = TaskViewController()
        let presenter = TaskPresenter()
        let interactor = TaskInteractor()
        let router = TaskRouter()
        
        viewController.presenter = presenter
        presenter.view = viewController
        presenter.interactor = interactor
        interactor.output = presenter
        
        return viewController
    }
}

Conclusion


The VIPER design pattern is a robust architecture for iOS development, offering clear separation of concerns, enhanced testability, and improved modularity. By adopting VIPER, you can create scalable and maintainable applications.


In summary, the VIPER pattern helps structure your iOS applications in a more organized and efficient way. It separates the data, user interface, and control logic, leading to a more maintainable and scalable codebase.


instructor

Exodai INSTRUCTOR!

Johan t'Sas

Owner and Swift developer!