iosswiftviper-architecture

How to pass ViewController Reference to Router in VIPER design pattern?


P.S: This is not a opinionated question. Its a legitimate doubt on wiring various modules in VIPER. Its a theoretical question so no code attached. I just need to know how do we wire up View-Presenter-Router in this specific case without breaking ground rules of VIPER

I am trying my hands-on with VIPER for the first time. Here is my fundamental understanding of VIPER.

View: Supposed to show UI Controls and capture IBActions and call its presenter's delegate method to process events

Presenter: Will process all the UI related data and prepares data for rendering and hands over the data back to View. Whenever a screen transition is required it calls its router and asks the router to perform transition

P.S: Presenter will not have any UIComponents in it. So no import UIKit statement in presenter.

Router: is responsible for performing screen transitions usually does it with the help of wireframes (optional but good to have such class in app)

Interactor: Contains all business logic.Presenter will call Interactor whenever processing based on business logic is required.

Entity: POJO classes (Simple Swift Objects or Core data entities).

Now comes the question:

If my assumptions are right, Presenter is supposed to be a plain Swift class with no UIKit access.

If true, assume I press a button on my ViewControllerA and I need to push another ViewControllerB on top of it, obviously ViewControllerA will talk to PresenterA and tells it that button is tapped, now PresenterA should talk to RouterA and tell it to push ViewControllerB.

Because Router can have access to UIKit I can easily create a new instance of ViewControllerB using storyboard instance or from xib, but in order to push that instance I need the ViewControllerA's instance.

But PresenterA can not hold a reference to ViewControllerA or can be passed as argument to PresenterA in function because UIViewController belongs to UIKit and Presenters are not supposed to have UI statements.

Probable Solutions I can think of:

Solution 1:

While creating Router instance, pass corresponding ViewController instance as a part of its init (Dependency injection phase) that way Router will always have reference to ViewController it belongs to

Solution 2:

Have a Router declare its protocol and implement it in ViewController and whenever reference to ViewController needed use delegate of Router. But this contradicts the rules of VIPER that routers are not supposed to talk to View.

Am I thinking straight? Am I right with my assumptions? If yes what is the correct way to deal with this problem, Please suggest


Solution

  • Any advice or opinion regarding VIPER for iOS applications is debatable, since VIPER does not fit strictly into iOS's UIKit design. But if I may put my two cents into discussion:

    First off, I would argue that UIViewController fits perfectly into the role of Presenter in VIPER pattern, therefore ViewControllerA doesn't need to talk to any class in between itself and Router - just communicate with Router directly.

    Secondly, there is supposed to be only one Router object - because application has only one view/navigation stack. For that reason it's ideal to implement a Singleton pattern for Router, so ViewControllerA (or Presenter, if you'd rather have your ViewControllers perform the role of View in that pattern) doesn't need to keep a reference to Router.

    Thirdly, you shouldn't need to pass the reference to ViewControllerA to your Router - Router should already have the reference to it, because Router was supposed to present it in the first place. Something like that:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    {
        // ...
    
        window?.rootViewController = Router.shared.rootViewController
    
        // ...
    }
    
    class Router
    {
        static let shared = Router()
    
        let rootViewController = ViewControllerA() // or UINavigationController, or UITabBarController etc.
    }
    

    Router should keep track of navigation stack and keep reference to currently presented ViewController

    But that is, like, my opinion.