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
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.