I'm building my first app using VIPER architecture. I have two VCs: the main one and the modal one (presented modally from the main VC). There is a tableView inside my modal VC, and when the user selects a row there, I need to pass it to presenter and then from presenter to the main VC. I also need to keep the selected row highlighted in the modal VC, so if I close it and then present it again, the row will be still highlighted. I'm confused, because I don't know what's the best way to do it. What I've tried is including my modal VC into configurator and calling configurator two times: in the main VC and then again in the modal VC. It works fine. That's what my configurator looks like:
protocol ObserverConfiguratorProtocol {
func configure(with mainViewController: ObserverViewController, with modalViewController: CurrenciesViewController)
}
class ObserverConfigurator: ObserverConfiguratorProtocol {
func configure(with mainViewController: ObserverViewController, with modalViewController: CurrenciesViewController) {
let presenter = ObserverPresenter(view: mainViewController)
let interactor = ObserverInteractor(presenter: presenter)
let router = ObserverRouter(view: mainViewController)
mainViewController.presenter = presenter
modalViewController.presenter = presenter
presenter.interactor = interactor
presenter.router = router
presenter.view = mainViewController
presenter.modalView = modalViewController
}
}
viewDidLoad() in the main VC:
override func viewDidLoad() {
configurator.configure(with: self, view: CurrenciesViewController())
}
viewDidLoad() in the modal VC:
override func viewDidLoad() {
configurator.configure(with: ObserverViewController(), view: self)
}
Anyway, I'm not sure it corresponds VIPER principles. Does it, or there are better solutions? Any help is appreciated!
If you are going to present another view controller, it should be on its own module for VIPER. ObserverViewController
should not know about the presence of CurrenciesViewController
.
Instead, only ObserverRouter
should know about how to build CurrenciesModule
and present and pass data into it. Something like this:
final class CurrenciesBuilder {
@available(*, unavailable) private init() { }
static func build(routerDelegate: ObserverRouterDelegate, selectedIndex: IndexPath?) -> CurrenciesViewProtocol {
let service = CurrenciesService()
let interactor = CurrenciesInteractor(service: service, selectedIndex: selectedIndex)
let view = CurrenciesViewController(nibName: String(describing: CurrenciesViewController.self), bundle: nil)
let router = CurrenciesRouter(vc: view)
router.delegate = routerDelegate
let presenter = CurrenciesPresenter(interactor: interactor,
view: view,
router: router)
view.presenter = presenter
return view
}
}
So, whenever you need to present CurrenciesViewController
, you could pass selectedIndex
and it can highlight it, if applicable. I would pass the information to the interactor, and let presenter know about it and do some changes in the view.
For passing the selectedIndex
back to ObserverViewController
, you could create a ObserverRouterDelegate
that can be triggered when CurrenciesViewController
is going to be dismissed.
protocol ObserverRouterDelegate: AnyObject {
func didDismiss(with selectedIndex: IndexPath?)
}
To wrap up; there should be 2 modules, ObserverModule
and CurrenciesModule
, that should have bidirectional connection through their routers. Since routers are responsible for presenting/dismissing views (and they hold a connection to their views), they can update their views and/or let the peer router update their own views.