<file_sep>//
//  TaskDetailProtocols.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

protocol TaskDetailViewProtocol: class {
    
    var presenter: TaskDetailPresenterProtocol? { get set }
    
    func showTask(_ task: TaskItem)
}

protocol TaskDetailPresenterProtocol: class {
    
    var view: TaskDetailViewProtocol? { get set }
    var interactor: TaskDetailPresenterToInteractorProtocol? { get set }
    
    func showDetail()
}

protocol TaskDetailPresenterToInteractorProtocol: class {
    var taskItem: TaskItem? { get set }
}

protocol TaskDetailRouterProtocol: class {
    
    static func getTaskDetailRouter(with task: TaskItem) -> UIViewController?
    
}
<file_sep>//
//  TaskDetailViewController.swift
//  TaskLogger
//
//  Created by <NAME> on 23/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskDetailViewController: UIViewController {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionTextView: UITextView!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }


}
<file_sep>//
//  TaskDetailPresenter.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskDetailPresenter: TaskDetailPresenterProtocol {
    
    var view: TaskDetailViewProtocol?
    var interactor: TaskDetailPresenterToInteractorProtocol?
    
    func showDetail() {
        guard let task = interactor?.taskItem else { return }
        view?.showTask(task)
    }
    

}
<file_sep>//
//  TaskItem.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

struct TaskItem {

    let title: String
    let description: String?
    
}
<file_sep>//
//  TasksManager.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TasksManager: NSObject {

    private override init() { }
    
    static let shared = TasksManager()
    
    var tasks: [TaskItem] = []
    
}
<file_sep>//
//  TaskDetailViewController.swift
//  TaskLogger
//
//  Created by <NAME> on 23/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskDetailViewController: UIViewController {

    var presenter: TaskDetailPresenterProtocol?
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionTextView: UITextView!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter?.showDetail()
    }


}

extension TaskDetailViewController: TaskDetailViewProtocol {
    
    func showTask(_ task: TaskItem) {
        titleLabel.text = task.title
        descriptionTextView.text = task.description
    }
    
 
}
<file_sep>//
//  TaskListViewController.swift
//  TaskLogger
//
//  Created by <NAME> on 23/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskListViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    //MARK: TableView Datasource
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UITableViewCell.self), for: indexPath)
        return cell
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }
    
    //MARK: TableView Delegate
    
    
    //MARK: Functions
    
    @IBAction func addTask(_ sender: UIBarButtonItem) {
     
    }

}
<file_sep>//
//  TaskDetailInteractor.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskDetailInteractor: TaskDetailPresenterToInteractorProtocol {
    var taskItem: TaskItem?
    
}
<file_sep>//
//  TaskListInteractor.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskListInteractor: TaskListPresenterToInteractorProtocol {
    
    var presenter: TaskListInteractorToPresenterProtocol?
    
    var tasksManager = TasksManager.shared
    var tasks: [TaskItem] {
        return tasksManager.tasks
    }
    
    func fetchTasks() {
        presenter?.didFetchTasks(tasks)
    }
    
    func saveTask(_ task: TaskItem) {
        if task.title.isEmpty {
            presenter?.onError(message: "Title cannot be empty")
        } else {
            tasksManager.tasks.append(task)
            presenter?.didAddTask(task)
        }
    }
    
    func updateTask(_ index: Int, title: String, description: String?) {
        if title.isEmpty {
            presenter?.onError(message: "Title cannot be empty")
        } else {
            let task = TaskItem(title: title, description: description)
            tasksManager.tasks[index] = task
            presenter?.didEditTasks()
        }
    }
    
    func deleteTask(_ index: Int) {
        tasksManager.tasks.remove(at: index)
        presenter?.didRemoveTask(index)
    }
    
}
<file_sep># TaskLogger
A sample app written in Swift, showing the use of VIPER

Read tutorial:
https://kodesnippets.com/charming-the-viper/
<file_sep>//
//  TaskDetailRouter.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskDetailRouter: TaskDetailRouterProtocol {
    
    static func getTaskDetailRouter(with task: TaskItem) -> UIViewController? {
        guard let taskDetailViewController = storyboard.instantiateViewController(withIdentifier: String(describing: TaskDetailViewController.self))  as? TaskDetailViewController else {
            return nil
        }
        
        let presenter = TaskDetailPresenter()
        taskDetailViewController.presenter = presenter
        presenter.view = taskDetailViewController
        let interactor = TaskDetailInteractor()
        interactor.taskItem = task
        presenter.interactor = interactor
        
        return taskDetailViewController
    }
    
    
    static var storyboard: UIStoryboard {
        return UIStoryboard(name: "Main", bundle: Bundle.main)
    }
    

}
<file_sep>//
//  TaskListProtocols.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

// MARK: VIEW

protocol TaskListViewProtocol: class {
    
    var presenter: TaskListPresenterProtocol? { get set }
    
    func showTasks(_ tasks: [TaskItem])
    func showMessage(_ message: String)
}

// MARK: PRESENTER

protocol TaskListPresenterProtocol: class {
    
    var view: TaskListViewProtocol? { get set }
    var interactor: TaskListPresenterToInteractorProtocol? { get set }
    var router: TaskListRouterProtocol? { get set }
    
    func getTasks()
    func showTaskDetail(_ task: TaskItem)
    func addTask(_ task: TaskItem)
    func editTask(_ index: Int, title: String, description: String?)
    func removeTask(_ index: Int)
}

// MARK: INTERACTOR

protocol TaskListPresenterToInteractorProtocol: class {
    
    var presenter: TaskListInteractorToPresenterProtocol? { get set }
    
    func fetchTasks()
    func saveTask(_ task: TaskItem)
    func updateTask(_ index: Int, title: String, description: String?)
    func deleteTask(_ index: Int)
}

protocol TaskListInteractorToPresenterProtocol: class {
    
    func didAddTask(_ task: TaskItem)
    func didRemoveTask(_ index: Int)
    func didFetchTasks(_ tasks: [TaskItem])
    func didEditTasks()
    func onError(message: String)
}

// MARK: ROUTER

protocol TaskListRouterProtocol: class {
    
    func presentTaskListDetail(from view: TaskListViewProtocol, for task: TaskItem)
}
<file_sep>//
//  TaskListViewController.swift
//  TaskLogger
//
//  Created by <NAME> on 23/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskListViewController: UITableViewController {

    var presenter: TaskListPresenterProtocol?
    var tasks: [TaskItem] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupPresenter()
        presenter?.getTasks()
    }
    
    //MARK: TableView Datasource
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UITableViewCell.self), for: indexPath)
        let tasks = self.tasks[indexPath.row]
        cell.textLabel?.text = tasks.title
        cell.detailTextLabel?.text = tasks.description
        return cell
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }
    
    
    //MARK: TableView Delegate
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let task = tasks[indexPath.row]
        presenter?.showTaskDetail(task)
    }
    
    override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        
        let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
            self.presenter?.removeTask(indexPath.row)
           }
        
        let edit = UITableViewRowAction(style: .default, title: "Edit") { (action, indexPath) in
            let task = self.tasks[indexPath.row]
            self.updateTask(index: indexPath.row, task: task)
        }
        edit.backgroundColor = UIColor.lightGray
        
        return [edit, delete]
        
    }
    
    //MARK: Functions
    
    private func setupPresenter() {
        let presenter: TaskListPresenter & TaskListInteractorToPresenterProtocol = TaskListPresenter()
        self.presenter = presenter
        let interactor = TaskListInteractor()
        self.presenter?.interactor = interactor
        interactor.presenter = presenter
        self.presenter?.router = TaskListRouter()
        self.presenter?.view = self
    }
    
    private func addTask() {
        let alertController = UIAlertController(title: "Add Task", message: "", preferredStyle: .alert)
        alertController.addTextField(configurationHandler: nil)
        alertController.addTextField(configurationHandler: nil)
        let saveAction = UIAlertAction(title: "Save", style: .default, handler: { [weak self] alert in
            let titleText = alertController.textFields![0].text ?? ""
            let descriptionText = alertController.textFields![1].text ?? "NA"
            let taskItem = TaskItem(title: titleText, description: descriptionText)
            self?.presenter?.addTask(taskItem)
        })
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        alertController.addAction(saveAction)
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }
    
    private func updateTask(index: Int, task: TaskItem) {
        let alertController = UIAlertController(title: "Update Task", message: "", preferredStyle: .alert)
        alertController.addTextField { $0.text = task.title }
        alertController.addTextField { $0.text = task.description }
        let updateAction = UIAlertAction(title: "Update", style: .default, handler: { [weak self] alert in
            let titleText = alertController.textFields![0].text ?? ""
            let descriptionText = alertController.textFields![1].text ?? "NA"
            self?.presenter?.editTask(index, title: titleText, description: descriptionText)
        })
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        alertController.addAction(updateAction)
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }
    
    private func showAlert(_ message: String) {
        let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alertController.addAction(okAction)
        present(alertController, animated: true, completion: nil)
    }
    
    @IBAction func addTask(_ sender: UIBarButtonItem) {
        addTask()
    }

}

extension TaskListViewController: TaskListViewProtocol {

    func showTasks(_ tasks: [TaskItem]) {
        self.tasks = tasks
        tableView.reloadData()
    }
    
    func showMessage(_ message: String) {
        showAlert(message)
    }
    
}
<file_sep>//
//  TaskListRouter.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskListRouter: TaskListRouterProtocol {
    
    func presentTaskListDetail(from view: TaskListViewProtocol, for task: TaskItem) {
        guard
            let tasklistViewController = view as? UIViewController,
            let taskDetail = TaskDetailRouter.getTaskDetailRouter(with: task) else {
            return
        }
        tasklistViewController.navigationController?.pushViewController(taskDetail, animated: true)
    }
    
}
<file_sep>//
//  TaskListPresenter.swift
//  TaskLogger
//
//  Created by <NAME> on 30/9/18.
//  Copyright © 2018 <NAME>. All rights reserved.
//

import UIKit

class TaskListPresenter: TaskListPresenterProtocol {
    
    var view: TaskListViewProtocol?
    var interactor: TaskListPresenterToInteractorProtocol?
    var router: TaskListRouterProtocol?
    
    func getTasks() {
        interactor?.fetchTasks()
    }
    
    func showTaskDetail(_ task: TaskItem) {
        guard let view = view else { return }
        router?.presentTaskListDetail(from: view, for: task)
    }
    
    func addTask(_ task: TaskItem) {
        interactor?.saveTask(task)
    }
    
    func editTask(_ index: Int, title: String, description: String?) {
        interactor?.updateTask(index, title: title, description: description)
    }
    
    func removeTask(_ index: Int) {
        interactor?.deleteTask(index)
    }
    

}

extension TaskListPresenter: TaskListInteractorToPresenterProtocol {
    
    func didAddTask(_ task: TaskItem) {
        interactor?.fetchTasks()
    }

    func didEditTasks() {
        interactor?.fetchTasks()
    }
    
    func didRemoveTask(_ index: Int) {
        interactor?.fetchTasks()
    }
    
    func didFetchTasks(_ tasks: [TaskItem]) {
        view?.showTasks(tasks)
    }

    func onError(message: String) {
        view?.showMessage(message)
    }
    
    
}
