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
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()
}
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?
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?
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?
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
}