ekazaev / route-composer

Protocol oriented, Cocoa UI abstractions based library that helps to handle view controllers composition, navigation and deep linking tasks in the iOS application. Can be used as the universal replacement for the Coordinator pattern.
MIT License
896 stars 63 forks source link

Hot to close screen/module using Router #58

Closed trimonovds closed 4 years ago

trimonovds commented 4 years ago

Hello! I am facing a problem using RouteComposer in my application. The problem is that the library allows you to define a way to open an arbitrary screen from anywhere in the application, which is awesome, but I did not understand how can i use the library (DefaultRouter) to close a screen that is open in this way. Let me give you an example

I have a default "Login" screen defined as: let loginStep = StepAssembly(finder: finder, factory: factory) .using(UINavigationController.push()) .from(NavigationControllerStep()) .using(GeneralAction.presentModally()) .from(GeneralStep.current()) .assemble() in my AppModulesFactory. When i want to route to auth screen, i use try? router.navigate(to: loginStep, with: nil) When user taps on "Sign In" button i have to write some code to remove LoginViewController (embeded in UINavigationController) from screen. Now i use the only found solution: try? DefaultRouter().navigate(to: GeneralStep.custom(using: PresentingFinder()), with: ()) from inside my LoginViewController. Although this method works at the moment, this code will break as soon as the definition of how the loginStep is defined change. For example if i change "Embed to UINavigationController -> Present" to "Push to theOneAndOnlyNavigationControllerInMyApp". The question is: Is there any foreseen way to "close" screen defined as DestinationStep and routed to using DefaultRouter.

To simplify my question there is another example: I described my screen to be opened as just simple push to some navigationController like that let phaseStep = StepAssembly(finder: finder, factory: factory) .using(UINavigationController.push()) .from(phasesScreen().expectingContainer()) .assemble() and I need to implement a button in PhaseViewController (phaseStep) which just invokes self.navigationController?.pop() (default Back button behavior) using DefaultRouter. How would you implement that in "correct" way. Thanks!

ekazaev commented 4 years ago

Hi @trimonovds

Sorry I am answering from my phone. Hopefully I understood the question right. So, the route composer does build parts of the view controllers graph. What it knows and can observe is only that is present in the graph and it cant do magic.

At first glance you have 2 options :

  1. You can use some custom proxy finder MyLoginFindingFinder(for: ClassFinder<Whatever>()) that finds the view controller in the graph before your login screen if it is present and will returns the previous one to the router as a starting point to build a new branch in your graph. Then Router will destroy everything after the starting view controller automatically. But may add some limitations to the way you write the configuration.

  2. I would personally use a custom RoutingInterceptor and attach it as a global interceptor to the router that you are using for the generic navigation. Generic - I mean all the navigation that is not related to the navigation within the login stack. In that custom router check your current stack using lets say a class finder directly, and if it finds the login scree in the graph - you hide it and then you return the control to the router, if your login screen is not present - you just return the control to the generic router immediately. This approach will give you the flexibility that no matter how you going to change the configuration of the login screen in the future - there will be only one place that is responsible for hiding it and you can update it accordingly. Meanwhile - if you need some navigation within the login stack - just use another instance of router without that interceptor attached. I believe the Example app uses another instance of router within LoginInterceptor event though for the different reasons (to avoid unnecessary security checks). This approach does not break the Single Responsibility Principle and allows you to control the behaviour using the appropriate strategy.

Hope I understood your question correctly. Please let me know if I did not or you need more details. Ill try to answer tomorrow.