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

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?

Related

Viper navigation using swinject assemblies

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()
])

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?

Exit Coordinator in Coodinator Pattern in swift

i am a beginner at Coordinator Pattern,
so i am trying to create a simple login screen
Load app -> Login screen -> Main Screen
i read an article by Hacking with Swift about Coordinator Pattern and still a bit confuse on how to exit an coordinator.
So how do i exit a coordinator?
From what i know, the parent coordinator are consist of the Login Coordinator and Main Screen Coordinator , in order to access the Main Screen Coordinator, you need to clear the child coordinator in the parent coordinator and then add the Main Screen Coordinator. is it correct?
So basically what i want to do is go back to the parent Coordinator and then setup the Min View Coordinator as the parent coordinator child
Thanks in advance
Parent Coordinator :
class AppCoordinator:NSObject,CoordinatorExtras {
private(set) var childCoordinators = [Coordinator]()
private let window:UIWindow
init(win:UIWindow){
self.window = win
}
func start() {
let navigationControler = UINavigationController()
let loginCoordinator = LoginCoordinator(navigationController: navigationControler)
childCoordinators.append(loginCoordinator)
loginCoordinator.parentCoordinator = self
loginCoordinator.start()
navigationControler.navigationBar.barStyle = .blackTranslucent
window.rootViewController = navigationControler
window.makeKeyAndVisible()
}
func userIsValid(_ child:Coordinator?) {
childDidFinnish(child)
let navigationControler = UINavigationController()
let tabBarCoordinator = TabBarCoordinator(navigationController: navigationControler)
childCoordinators.append(tabBarCoordinator)
tabBarCoordinator.start()
}
func childDidFinnish(_ child:Coordinator?) {
for (idx,coordinator) in childCoordinators.enumerated() {
if coordinator === child {
childCoordinators.remove(at: idx)
}
}
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard let fromVC = navigationController.transitionCoordinator?.viewController(forKey: .from) else { return }
if navigationController.viewControllers.contains(fromVC) {
return
}
if let homeVC = fromVC as? LoginViewController {
childDidFinnish(homeVC.coordinator)
}
}
}
The childDidFinish function is based on Paul's but i have no idea on how to use it
So this is how i tried to implement it
Login Cordinator:
final class LoginCoordinator:NSObject, Coordinator {
weak var parentCoordinator:AppCoordinator?
private(set) var childCoordinators: [Coordinator] = []
private let navigationController : UINavigationController
private let loginVC = LoginViewController.instantiate()
init (navigationController:UINavigationController) {
self.navigationController = navigationController
}
func start() {
navigationController.delegate = self
loginVC.coordinator = self
navigationController.isNavigationBarHidden = true
navigationController.pushViewController(loginVC, animated: true)
}
func gotoLogin() {
navigationController.popToRootViewController(animated: true)
}
func gotoRegister(){
let registerVC = RegisterViewController.instantiate()
registerVC.coordinator = self
navigationController.pushViewController(registerVC, animated: true)
}
func gotoHome() {
let tabBarCoordinator = TabBarCoordinator(navigationController: navigationController)
childCoordinators.append(tabBarCoordinator)
tabBarCoordinator.start()
}
func gotoTerdaftar() {
let terdaftarVC = ListKorperasiViewController()
terdaftarVC.coordinator = self
navigationController.pushViewController(terdaftarVC, animated: true)
}
func popView() {
navigationController.popViewController(animated: true)
}
//Check if user is login or not
func userIsLogin() {
parentCoordinator?.userIsValid(self)
}
}
you can add a closure to your child coordinator
var finish: (() -> Void)?
And associate a value to this closure in your parent coordinator.
let loginCoordinator = LoginCoordinator(navigationController: navigationControler)
childCoordinators.append(loginCoordinator)
loginCoordinator.finish = { do something like remove coordinator from childCoordinators and start a new coordinator }
loginCoordinator.start()

How to pass chain view controller presenter with observable

I'm new in the RxSwift development and I've an issue while presentation a view controller.
My MainViewController is just a table view and I would like to present detail when I tap on a item of the list.
My DetailViewController is modally presented and needs a ViewModel as input parameter.
I would like to avoid to dismiss the DetailViewController, I think that the responsability of dismiss belongs to the one who presented the view controller, i.e the dismiss should happen in the MainViewController.
Here is my current code
DetailsViewController
class DetailsViewController: UIViewController {
#IBOutlet weak private var doneButton: Button!
#IBOutlet weak private var label: Label!
let viewModel: DetailsViewModel
private let bag = DisposeBag()
var onComplete: Driver<Void> {
doneButton.rx.tap.take(1).asDriver(onErrorJustReturn: ())
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
bind()
}
private func bind() {
let ouput = viewModel.bind()
ouput.id.drive(idLabel.rx.text)
.disposed(by: bag)
}
}
DetailsViewModel
class DetailsViewModel {
struct Output {
let id: Driver<String>
}
let item: Observable<Item>
init(with vehicle: Observable<Item>) {
self.item = item
}
func bind() -> Output {
let id = item
.map { $0.id }
.asDriver(onErrorJustReturn: "Unknown")
return Output(id: id)
}
}
MainViewController
class MainViewController: UIViewController {
#IBOutlet weak private var tableView: TableView!
private var bag = DisposeBag()
private let viewModel: MainViewModel
private var detailsViewController: DetailsViewController?
override func viewDidLoad(_ animated: Bool) {
super.viewDidLoad(animated)
bind()
}
private func bind() {
let input = MainViewModel.Input(
selectedItem: tableView.rx.modelSelected(Item.self).asObservable()
)
let output = viewModel.bind(input: input)
showItem(output.selectedItem)
}
private func showItem(_ item: Observable<Item>) {
let viewModel = DetailsViewModel(with: vehicle)
detailsViewController = DetailsController(with: viewModel)
item.flatMapFirst { [weak self] item -> Observable<Void> in
guard let self = self,
let detailsViewController = self.detailsViewController else {
return Observable<Void>.never()
}
self.present(detailsViewController, animated: true)
return detailsViewController.onComplete.asObservable()
}
.subscribe(onNext: { [weak self] in
self?.detailsViewController?.dismiss(animated: true)
self?.detailsViewController? = nil
})
.disposed(by: bag)
}
}
MainViewModel
class MainViewModel {
struct Input {
let selectedItem: Observable<Item>
}
struct Output {
let selectedItem: Observable<Item>
}
func bind(input: Input) -> Output {
let selectedItem = input.selectedItem
.throttle(.milliseconds(500),
latest: false,
scheduler: MainScheduler.instance)
.asObservable()
return Output(selectedItem: selectedItem)
}
}
My issue is on showItem of MainViewController.
I still to think that having the DetailsViewController input as an Observable isn't working but from what I understand from Rx, we should use Observable as much as possible.
Having Item instead of Observable<Item> as input could let me use this kind of code:
item.flatMapFirst { item -> Observable<Void> in
guard let self = self else {
return Observable<Void>.never()
}
let viewModel = DetailsViewModel(with: item)
self.detailsViewController = DetailsViewController(with: viewModel)
guard let detailsViewController = self.detailsViewController else {
return Observable<Void>.never()
}
present(detailsViewController, animated: true)
return detailsViewController
}
.subscribe(onNext: { [weak self] in
self?.detailsViewController?.dismiss(animated: true)
self?.detailsViewController = nil
})
.disposed(by: bag)
What is the right way to do this?
Thanks
You should not "use Observable as much as possible." If an object is only going to ever have to deal with a single item, then just pass the item. For example if a label is only ever going to display "Hello World" then just assign the string to the label's text property. Don't bother wrapping it in a just and binding it to the label's rx.text.
Your second option is much closer to what you should have. It's a fine idea.
You might find my CLE library interesting. It takes care of the issue you are trying to handle here.

How to handle navigation with observables using Rx-MVVM-C

Hello I am trying to do a project with RxSwift and I am stuck trying to do in a properly way the connection between the Coordinator and the ViewModel.
Goal
Using observables, the Coordinator receives and event (in that case, when a row has been tapped) then does whatever.
Scenario
Giving a Post (String)
typealias Post = String
I have the following Coordinator:
class Coordinator {
func start() {
let selectedPostObservable = PublishSubject<Post>()
let viewController = ViewController()
let viewModel = ViewModel()
viewController.viewModel = viewModel
selectedPostObservable.subscribe { post in
//Do whatever
}
}
}
The selectedPostObservable is what I don't know how to connect it in a "clean" way with the viewModel.
As ViewModel:
class ViewModel {
struct Input {
let selectedIndexPath: Observable<IndexPath>
}
struct Output {
//UI Outputs
}
func transform(input: Input) -> Output {
let posts: [Post] = Observable.just(["1", "2", "3"])
//Connect with selectedindex
let result = input.selectedIndexPath
.withLatestFrom(posts) { $1[$0.row] }
.asDriver(onErrorJustReturn: nil)
return Output()
}
}
The result variable is what I should connect with selectedPostObservable.
And the ViewController (although I think is not relevant for the question):
class ViewController: UIViewController {
//...
var viewModel: ViewModel!
var tableView: UITableView!
//...
func bindViewModel() {
let input = ViewModel.Input(selectedIndexPath: tableView.rx.itemSelected.asObservable())
viewModel.transform(input: input)
}
}
Thank you so much.
Working with the structure you are starting with, I would put the PublishSubject in the ViewModel class instead of the Coordinator. Then something like this:
class ViewModel {
struct Input {
let selectedIndexPath: Observable<IndexPath>
}
struct Output {
//UI Outputs
}
let selectedPost = PublishSubject<Post>()
let bag = DisposeBag()
func transform(input: Input) -> Output {
let posts: [Post] = Observable.just(["1", "2", "3"])
//Connect with selectedindex
input.selectedIndexPath
.withLatestFrom(posts) { $1[$0.row] }
.bind(to: selectedPost)
.disposed(by: bag)
return Output()
}
}
class Coordinator {
func start() {
let viewController = ViewController()
let viewModel = ViewModel()
viewController.viewModel = viewModel
viewModel.selectedPost.subscribe { post in
//Do whatever
}
.disposed(by: viewModel.bag)
}
}