/Swift-VIPER-iOS

SwiftVIPER is an sample iOS App written in Swift using the VIPER architecture. Also SwiftVIPER is not a strict VIPER architecture.

Primary LanguageSwiftMIT LicenseMIT

SwiftVIPER (View, Interactor, Presenter, Entity, Router)

SwiftVIPER is an sample iOS App written in Swift using the VIPER architecture.
Also SwiftVIPER is not a strict VIPER architecture.
Part of the project is a unique way.

Language

Sample App

Architecture

Run Sample

  1. Clone this repository.

    git clone git@github.com:yokurin/Swift-VIPER-iOS.git
    
  2. Open SwiftVIPER.xcodeproj in Xcode.

  3. Run

Description

SwiftVIPER is not a strict VIPER architecture.
Part of the project is a unique way. This is just a suggestion.

View (including UIViewController)

View must implement Viewable. Viewable has Default Extension.
※ View is not just View like UIView etc in this case.

protocol Viewable: AnyObject {
    func push(_ vc: UIViewController, animated: Bool)
    func present(_ vc: UIViewController, animated: Bool)
    func pop(animated: Bool)
    func dismiss(animated: Bool)
    func dismiss(animated: Bool, completion: @escaping (() -> Void))
}

extension Viewable where Self: UIViewController {

    func push(_ vc: UIViewController, animated: Bool) {
        self.navigationController?.pushViewController(vc, animated: animated)
    }

    func present(_ vc: UIViewController, animated: Bool) {
        self.present(vc, animated: animated, completion: nil)
    }

    func pop(animated: Bool) {
        self.navigationController?.popViewController(animated: animated)
    }

    func dismiss(animated: Bool) {
        self.dismiss(animated: animated, completion: nil)
    }

    func dismiss(animated: Bool, completion: @escaping (() -> Void)) {
        self.dismiss(animated: animated, completion: completion)
    }
}

Example

protocol ViewInputs: AnyObject {
    
}

protocol ViewOutputs: AnyObject {
    func viewDidLoad()
}

final class ListViewController: UIViewController {

    internal var presenter: ViewOutputs?

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

}

extension ListViewController: ListViewInputs {}

extension ListViewController: Viewable {}

Interactor

Interactor must implement Interactorable. But Interactorable has no properties.

protocol Interactorable {
    // nop
}

Example

protocol InteractorOutputs: AnyObject {

}

final class Interactor: Interactorable {

    weak var presenter: InteractorOutputs?
    
}

Presenter

Presenter must implement Presenterable.

protocol Presenterable {
    associatedtype I: Interactorable
    associatedtype R: Routerable
    var dependencies: (interactor: I, router: R) { get }
}

Example

/// Must not import UIKit

typealias PresenterDependencies = (
    interactor: Interactor,
    router: RouterOutput
)

final class Presenter: Presenterable {
    
    ...
    
    internal var entities: Entities
    private weak var view: ViewInputs!
    let dependencies: PresenterDependencies

    init(entities: Entities,
         view: ViewInputs,
         dependencies: PresenterDependencies)
    {
        self.view = view
        self.entities = entities
        self.dependencies = dependencies
    }
    
    ...
}

Entity

Entity has no protocol.

Example

struct EntryEntity {}

final class Entities {
    let entryEntity: EntryEntity
    
    var entities: [SomeEntity] = []

    init(entryEntity: EntryEntity) {
        self.entryEntity = entryEntity
    }
}

Router

Router must implement Routerable.

protocol Routerable {
    var view: Viewable! { get }

    func dismiss(animated: Bool)
    func dismiss(animated: Bool, completion: @escaping (() -> Void))
    func pop(animated: Bool)
}

extension Routerable {
    func dismiss(animated: Bool) {
        view.dismiss(animated: animated)
    }

    func dismiss(animated: Bool, completion: @escaping (() -> Void)) {
        view.dismiss(animated: animated, _completion: completion)
    }

    func pop(animated: Bool) {
        view.pop(animated: animated)
    }
}

Example

struct RouterInput {

    private func view(entryEntity: EntryEntity) -> ViewController {
        let view = ViewController()
        let interactor = Interactor()
        let dependencies = PresenterDependencies(interactor: interactor, router: RouterOutput(view))
        let presenter = Presenter(entities: Entities(entryEntity: entryEntity), view: view, dependencies: dependencies)
        view.presenter = presenter
        interactor.presenter = presenter
        return view
    }

    func push(from: Viewable, entryEntity: EntryEntity) {
        let view = self.view(entryEntity: entryEntity)
        from.push(view, animated: true)
    }

    func present(from: Viewable, entryEntity: EntryEntity) {
        let nav = UINavigationController(rootViewController: view(entryEntity: entryEntity))
        from.present(nav, animated: true)
    }
}

final class RouterOutput: Routerable {

    private(set) weak var view: Viewable!

    init(_ view: Viewable) {
        self.view = view
    }

    func transitionToOther() {
        OtherRouterInput().push(from: view, entryEntity: OtherEntryEntity())
    }
}

Unit Test

WIP ...

Xcode Template ( xctemplate )

WIP ...

Requirements

  • Xcode 10.0+
  • Swift 4.2+

Installation

git clone git@github.com:yokurin/Swift-VIPER-iOS.git

Author

Tsubasa Hayashi, yoku.rin.99@gmail.com

License

SwiftVIPER is available under the MIT license. See the LICENSE file for more info.