I am experimenting with routing in an iOS app.
My current approach is hit a road block however, I am creating a ViewControllerFactory and a NavigationControllerRouter.
The challenge I have is that the ViewControllerFactory needs to instruct the router on which route is required next and the NavigationControllerRouter requires the factory to pass a view controller to the navigation controller.
How can these 2 components communicate without creating a strong coupling?
enum Route {
case login
case home
}
protocol ViewControllerFactory {
func create(for route: Route) -> UIViewController
}
class VCFactory: ViewControllerFactory {
func create(for route: Route) -> UIViewController {
switch route {
case .login:
let viewController = UIViewController()
viewController.view.backgroundColor = .purple
return viewController
case .home:
let viewController = UIViewController()
viewController.view.backgroundColor = .red
return viewController
}
}
}
class NavigationControllerRouter {
private let navigationController: UINavigationController
private let factory: ViewControllerFactory
init(_ navigationController: UINavigationController, factory: ViewControllerFactory) {
self.navigationController = navigationController
self.factory = factory
}
func route(to route: Route) {
let viewController = factory.create(for: route)
navigationController.pushViewController(viewController, animated: true)
}
}
I set this up as follows
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private lazy var navController = UINavigationController()
private lazy var router: NavigationControllerRouter = {
let viewControllerFactory = VCFactory()
return NavigationControllerRouter(navController, factory: viewControllerFactory)
}()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: scene)
configure(window)
}
func configure(_ window: UIWindow) {
self.window = window
self.window?.makeKeyAndVisible()
self.window?.rootViewController = navController
router.route(to: .login)
}
}
I had considered passing a weak reference for the router into the view controller factory, then injecting that into any rendered views, however this then couples the 2, especially if I decide to use the view factory for something like child views, which shouldn't know about the router at all.