Viper navigation using swinject assemblies - swift

Currently i'm creating my modules like this:
final class DiscoverAssembly {
func build() -> UIViewController? {
let container = Container()
container.register(DiscoverInteractorInput.self) { (_, presenter: DiscoverPresenter) in
let interactor = DiscoverInteractor()
interactor.output = presenter
return interactor
}
container.register(DiscoverPresenterInput.self) { (r, viewController: DiscoverViewController) in
let presenter = DiscoverPresenter()
presenter.output = viewController
presenter.interactor = r.resolve(DiscoverInteractorInput.self, argument: presenter)
presenter.router = r.resolve(DiscoverRouterInput.self, argument: viewController)
return presenter
}
container.register(DiscoverRouterInput.self) { (_, viewController: DiscoverViewController) in
let router = DiscoverRouter()
router.viewController = viewController
return router
}
container.register(DiscoverViewController.self) { r in
let viewController = DiscoverViewController()
viewController.presenter = r.resolve(DiscoverPresenterInput.self, argument: viewController)
return viewController
}
return container.resolve(DiscoverViewController.self)
}
}
To navigate to other module i can just call build() in router and push/present specific VC.
If i will use Swinject assembly:
final class DiscoverAssembly: Assembly {
func assemble(container: Container) {
container.register(DiscoverInteractorInput.self) { (_, presenter: DiscoverPresenter) in
let interactor = DiscoverInteractor()
interactor.output = presenter
return interactor
}
container.register(DiscoverPresenterInput.self) { (r, viewController: DiscoverViewController) in
let presenter = DiscoverPresenter()
presenter.output = viewController
presenter.interactor = r.resolve(DiscoverInteractorInput.self, argument: presenter)
presenter.router = r.resolve(DiscoverRouterInput.self, argument: viewController)
return presenter
}
container.register(DiscoverRouterInput.self) { (_, viewController: DiscoverViewController) in
let router = DiscoverRouter()
router.viewController = viewController
return router
}
container.register(DiscoverViewController.self) { r in
let viewController = DiscoverViewController()
viewController.presenter = r.resolve(DiscoverPresenterInput.self, argument: viewController)
return viewController
}
}
}
Obviously, i can't get my VC directly from assembly. I need to have a main Assembler to resolve specific VC. So the question is: what is the best way to organise it? I can create an assembler in AppAssembler let's say and use it as a singletone in any router, but i'm not sure if it's okay. Also, in this case i have to register all my modules in one place. Imagine if my app has 30 modules...
let assembler = Assembler([
ServiceAssembly(),
ManagerAssembly()
])

Related

Swift, iOS: How to get Polymorphic and Decouple Coordinator from concrete type

I have these protocols:
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func mainViewController() -> UIViewController
}
And I created a MainCoordinator that conforms to this protocol and I pass a factory that allows me to decouple the coordinator from creating and capturing a concrete type so it can be polymorphic and can be used with more implementations of UIViewController either as rootViewControllers and mainMenuViewController as shown below:
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
}
start() {
guard let mainVC = factory.mainViewController() as? MainViewController, let rootViewController = rootViewController as? UINavigationViewController else { return }
mainVC.delegate = self
rootViewController.push(mainVC, animated: true)
}
As you can see, although I've created the coordinator to accept any subclass of UIViewController it has been coupled in the start function to the concrete implementation of UIViewController: MainViewController.
So my question is how to decouple it from MainViewController and have it more polymorphic?
You can pass coordinator as a parameter type in factory function and set delegate directly in factory function while creating controller instance. That way you wouldn’t have to expose controller type explicitly out of factory classes.
I came up with below approach.
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?
}
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
self.factory = factory
}
func start() {
guard let controller = factory.getViewController(delegateType: .MainCoordinator, delegateObject: self),let rootViewController = rootViewController as? UINavigationViewController else {
return
}
rootViewController.push(mainVC, animated: true)
}
}
extension MainCoordinator:DelegateCaller{
func printHello() {
print("helloo")
}
}
enum CoordinatoreTypes{
case MainCoordinator
case none
}
class Factory:UIViewControllerFactory{
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?{
switch delegateType{
case .MainCoordinator:
let controller = MainViewController()
controller.delegate = delegateObject as? MainCoordinator
return controller
case .none:
break
}
return nil
}
}
class MainViewController:UIViewController{
weak var delegate:DelegateCaller?
}
protocol DelegateCaller:AnyObject{
func printHello()
}

Router navigation in VIPER + Swinject

I have mainAssembly:
final class ApplicationAssembly {
var assembler: Assembler
init(with assemblies: [Assembly]) {
self.assembler = Assembler(assemblies)
}
}
With two module for example:
var applicationAssembly = ApplicationAssembly(with: [ProfileAssembly(), StoriesAssembly()])
window?.rootViewController = applicationAssembly.assembler.resolver.resolve(ProfileViewController.self)
Here is ProfileAssembly:
import Swinject
class ProfileAssembly: Assembly {
func assemble(container: Container) {
container.register(ProfileInteractorInput.self) { (r, presenter: ProfilePresenter) in
let interactor = ProfileInteractor()
interactor.output = presenter
return interactor
}
container.register(ProfilePresenterInput.self){ (r, viewController: ProfileViewController) in
let presenter = ProfilePresenter()
presenter.output = viewController
presenter.interactor = r.resolve(ProfileInteractorInput.self, argument: presenter)
presenter.router = r.resolve(ProfileRouterInput.self, argument: viewController)
return presenter
}
container.register(ProfileRouterInput.self) { (r, viewController: ProfileViewController) in
let router = ProfileRouter()
router.viewController = viewController
return router
}
container.register(ProfileViewController.self) { r in
let viewController = ProfileViewController()
viewController.presenter = r.resolve(ProfilePresenterInput.self, argument: viewController)
return viewController
}
}
}
Everything works fine, however i don't know how to configure my router to navigate to another module:
protocol ProfileRouterInput: class {
func openStories()
}
class ProfileRouter: ProfileRouterInput {
weak var viewController: UIViewController?
func openStories() {
}
}
Before Swinject I've used wireframes where I had something like build() function to return specific VC. But since my assemblies are in the main Assembler container, should I have a direct access from any routers in my app to the main assembler?

Why doesn't reflection work for optional types?

I am sending the viewController object to the manager class and reaching the views in the viewController with refleciton. However, if I define viewController optional, it cannot find reflection views.
class ClientManager {
var viewController : UIViewController? // not working var viewController : UIViewController is working why?
init (_ viewController: UIViewController) {
self.viewController = viewController
}
....
}
My reflection method is ;
private func prepareTarget(obj: Any, selector : String?,cellIdentifier :String?)
-> UIView?
{
let mirror = Mirror(reflecting: obj)
var result : UIView?
for (prop, val) in mirror.children {
if prop == selector {
result = val as? UIView
break
}
if val is UITableView {
let visibleCells = (val as! UITableView).visibleCells
for cell in visibleCells {
if result == nil {
result = prepareTarget(obj: cell,selector:
selector, cellIdentifier: cellIdentifier)
}
}
}
return result
}
How can I solve this problem?

Shared container with assembly - how to pass same objects to Coordinator and Controller

Everytime we resolve a protocol/class then we get a new object. My coordinator needs a view model and my controller needs the same view model.
internal class LoginFactory: Assembly {
func assemble(container: Container) {
let delegate = UIApplication.shared.delegate as? AppDelegate
container.register(LSViewModel.self) { res in
return LSViewModel()
}
container.register(LSCoordinator.self, factory: { res in
let cord = LSCoordinator(window: (delegate?.window)!)
cord.viewModel = res.resolve(LSViewModel.self)
return cord
})
container.register(LSViewController.self) { res in
let cont = StoryboardScene.Login.lsViewController.instantiate()
cont.viewModel = res.resolve(LSCoordinator.self)?.viewModel
return cont
}
}
}
The coordinator goes like
internal final class LSCoordinator: BaseCoordinator<Void>, Coordinator, HasDisposeBag {
private let window: UIWindow
weak var navigationController: UINavigationController!
//LSViewModel implements LSViewModelType
var viewModel: LSViewModelType!
init(window: UIWindow) {
self.window = window
}
func start() -> Observable<Void> {
let lslViewController: LSViewController = DependencyManager.getResolver().resolve(LSViewController.self)!
navigationController = UINavigationController(rootViewController: lsController)
viewModel.outputs.doneTapped
.subscribe(onNext: { [weak self] in self?.showLoginTypes() }).disposed(by: disposeBag)
window.rootViewController = navigationController
window.makeKeyAndVisible()
return .never()
}
func showLoginTypes() {
print(“blah blah”)
}
}
The problem is, when I am trying to inject viewModel in my lsViewController then a different instance of lsViewModel is created. As a result the Rx bindings are not working and the print statement is not executed. Is there any way to pass the same view model to both coordinator and controller?

Object conforming to a protocol should hide the protocol methods

I have an app designed with Viper architecture. To avoid exeptions, each module is created by a factory class which comply to BaseFactory protocol.
Two of one hundred (2%) modules in my app should be created with a custom factory method which is not enforced via protocol, a factory method which accept an argument.
Is it possible to "hide/disable" a function createViperModule() in the MemberProfileFactory class?
protocol BaseFactory {
static func createViperModule () -> UIViewController
}
class HelloFactory: BaseFactory {
static func creatViperModule() -> UIViewController {
let p = HelloPresenter()
let storyboard = UIStoryboard.init(name: "Hello", bundle: nil)
let vc = (storyboard.instantiateInitialViewController() as? HelloVC)!
p.vc = vc
vc.p = p
return vc
}
}
class MemberProfileFactory: BaseFactory {
static func createViperModule() -> UIViewController {
return PublicProfileVC()
}
static func createViperModule(withMember member: MemberModel) -> UIViewController {
let p = MemberProfilePresenter()
let storyboard = UIStoryboard.init(name: "MemberProfile", bundle: nil)
let vc = (storyboard.instantiateInitialViewController() as? MemberProfileVC)!
p.vc = vc
p.user = user
vc.p = p
return vc
}
}
You can't make 'createViperModule' private because of 'createViperModule' declared as internal in 'BaseFactory'. but you can declare it optional so it is not mandatory to implement.
#objc protocol BaseFactory {
#objc optional static func createViperModule () -> UIViewController
}