-
Notifications
You must be signed in to change notification settings - Fork 5
Routing
Routing in this architectural model is a plug-in implemented through events listener. Whenever an event can be transformed as a routing event the RoutingEventsListener
interested in that particular event will transform the event into a routing step. Each RoutingEventsListener
has a reference to a Router that then will be able to transform the RoutingStep into an actual UI action (push, present and so on).
Routers are the only objects that have a strong connection with the module manager so that they can ask it for a viewController to perform a specific routing step.
Routers have references to the module manager, but this still happens through abstraction; in fact Routers use ViewControllerFactory. (Routers do not know about the existence of modules)
When a router is asked to perform a routing step, the router will ask the module manager to transform the routing step into a viewController they can use to push/present on screen.
We must not forget that routing is something that cannot be abstracted from the fact that a screen exists. On import of a specific module, the existence of this module is exposed to the rest of the main target via an extension of ModuleRoutingStep
in which a static function will be responsible for building a ModuleRoutingStep
instance with the right module making object.
Example:
ProductDetailPageStep.swift
import ProductDetailPageModule //In an healthy app this should be the only time where the import for this module is made
typealias PDPEvents = ProductDetailPageEvents
extension ModuleRoutingStep {
static func product(routingContext: RoutingContext = .mainFlow, id: String, product: ProductProtocol?) -> ModuleRoutingStep {
return ModuleRoutingStep(withMaker: PDPMaker(routingContext: routingContext.rawValue, productId: id, product: product))
}
}
//PDPMaker is an object defined within the ProductDetailPageModule
The module manager will know how to use a ModuleRoutingStep
to build a module, extract its rootViewController and return it to the router.
For routing purposes a module routing step is not enough for the router to understand the behaviour the new controller should have in presentation. For this purpose, the module Routing Step must be wrapped into a PresentableRoutingStep
. This object will simply contain the preferences in presenting the new view controller on screen.
public struct PresentableRoutingStep {
public let step: ModuleRoutingStep
public let presentationMode: RoutingStepPresentationMode
public let modalPresentationStyle: UIModalPresentationStyle
}
The RoutingStepPresentationMode
is what helps the router to understand the presentation behaviour.
public enum RoutingStepPresentationMode {
case push(withCloseButton: Bool, onClose: (()->())?)
case modal
case modalWithNavigation(withCloseButton: Bool, onClose: (()->())?)
}
Router
in MERLin' is a protocol. This protocol has protocol extensions to provide default implementation for the methods
@discardableResult func route(to destination: PresentableRoutingStep) -> UIViewController?
@discardableResult func route(toDeeplink deeplink: String) -> UIViewController?
we leave to the concrete implementation to define how to handle shortcuts and the rootViewController to be used as root of the window in the app delegate.
The default implementation of the routing functions will inspect the viewController stack and will try to fulfil the preferences expressed in the presentableRoutingStep. The success of this attempt depends obviously from the view stack. If you try to push a view controller while the top view controller is not an UINavigationController the router will present the new ViewController modally as fallback.
The router protocol extension also provides a default behaviour for Deeplinking.