I'm trying to understand how to use XCoordinator to set a root view controller from a child coordinator.
I have an app delegate that runs the app coordinator.
The pattern I have is:
AppDelegate - App Coordinator --- Main Menu Coordinator --- Game Coorinator
(see image)
The app coordinator should not know about specific view controllers directly because I believe the view controllers should managed by their own respective coordinator.
Thus, the app coordinator should manage the flow between the coordinators and set a root, initial coordinator.
Sadly, the screen is blank, and keeps complaining that I can't as "Pushing a navigation controller is not supported".
But if I directly instantiate the view controller inside my appcoordinator its fine and I don't know why.
// AppCoordinator
enum AppRoute: Route {
case mainmenu
case game
}
class AppCoordinator: NavigationCoordinator<AppRoute> {
init() {
super.init(initialRoute: .mainmenu)
}
// MARK: Overrides
override func prepareTransition(for route: AppRoute) -> NavigationTransition {
let router = MainMenuCoordinator().strongRouter
return .push(router)
}
}
This should launch the MainMenuCoordinator's initial view controller
enum MainMenuRoute: Route {
case mainmenu
}
// MainMenuCoordinator
class MainMenuCoordinator: NavigationCoordinator<MainMenuRoute> {
init() {
super.init(initialRoute: .mainmenu)
}
override func prepareTransition(for route: MainMenuRoute) -> NavigationTransition {
print ("route: \(route as Any)")
let vc = MainMenuViewController.instantiate(.main)
return .push(vc)
}
}
But this returns a blank screen and it complaining that push is not supported.
But, if I move this code:
let vc = MainMenuViewController.instantiate(.main)
return .push(vc)
to the AppCoordinator
like this:
// MARK: AppCoordinator Overrides
override func prepareTransition(for route: AppRoute) -> NavigationTransition {
let vc = MainMenuViewController.instantiate(.main)
return .push(vc)
}
Its fine. A screen is presented -- but the main menu coordinator is no longer being "followed"
I'm wondering -- how do you make it so that the app coordinator just delegates responsibility to its child coordinator?
I appreciate any assistance you can give. Thanks
// Edit: I have tried another attempt, following an answer provided, of passing a reference of the parent coordinator to child coordinators.
I want the mainmenucoordinator
to use its own collection of routes; I don't want to just rely upon on any routes defined in the AppRoutes
.
So my app coordinator has these routes:
enum AppRoute: Route {
case mainmenu
case game
}
class AppCoordinator: NavigationCoordinator<AppRoute> {
init() {
let defaultRoute: AppRoute = .mainmenu
super.init(initialRoute: defaultRoute)
self.rootViewController.view.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
}
}
My main coordinator has these routes:
enum MainMenuRoute: Route {
case mainmenu
case selectPlayer
case playgame
}
class MainMenuCoordinator: NavigationCoordinator<MainMenuRoute> {
// For my child coordinators, keep a strong reference to parent
private var router: StrongRouter<AppRoute>?
/// ...
}
When I come to init it, I set the parent.
// MainMenuCoordinator: Init
convenience init(router: StrongRouter<AppRoute>) {
self.init(initialRoute: .mainmenu)
}
But now it won't use any routes that I've defined for the MainMenuCoordinator
; instead, it uses AppRoute
Example:
Trying to set the router in MainMenuCoordinator.prepareTransition
override func prepareTransition(for route: MainMenuRoute) -> NavigationTransition {
print ("route: \(route as Any)")
switch route {
case .mainmenu:
let vc = MainMenuViewController.instantiate(.main)
vc.router = strongRouter
return .push(vc)
// ....
}
}
The main menu view controller holds a reference to:
class MainMenuViewController: UIViewController, Storyboarded {
// MARK: - Stored properties
var router: UnownedRouter<MainMenuRoute>!
}
With these edits, the root view controller is now white; but the logger still complains I cannot push view controllers.
I am doing smth like this:
class MainCoordinator: NavigationCoordinator<ApplicationRoute> {
convenience init() {
var defaultRoute: ApplicationRoute = .showAuthentification
if ApplicationSettings.shared.isLoggedIn {
defaultRoute = .showApplicationRoot
}
self.init(defaultRoute: defaultRoute)
self.rootViewController.view.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
}
override func prepareTransition(for route: ApplicationRoute) -> NavigationTransition {
switch route {
case .showAuthentification:
return .multiple(.dismissToRoot(animation: .fadeInstant),
.presentFullScreen(AuthentificationCoordinator(router: strongRouter),
animation: .fadeInstant))
case .showApplicationRoot:
return .multiple(.dismissToRoot(animation: .fadeInstant),
.presentFullScreen(TabControllerCoordinator(router: strongRouter),
animation: .fadeInstant))
}
}
}
and in a one child coordinator:
class AuthentificationCoordinator: NavigationCoordinator<AuthentificationRoute> {
// For my child coordinators I keep a strong reference to the parent, so I can use it later
private var router: StrongRouter<SMCApplicationRoute>?
convenience init(router: StrongRouter<SMCApplicationRoute>) {
self.init(defaultRoute: .initial)
self.router = router
}
override func prepareTransition(for route: AuthentificationRoute) -> NavigationTransition {
switch route {
case .initial:
let initialVC = LandingPageViewController.loadFromXIB(type: LandingPageViewController.self)
initialVC.router = strongRouter
return .push(initialVC, animation: .fade)
case .showLogin:
let loginVC = LoginViewController.loadFromXIB(type: LoginViewController.self)
loginVC.router = strongRouter
return .push(loginVC, animation: .fade)
case .showHome:
// Here I trigger the other route from MainCoordinator. If I log in, the AuthentificationCoordinator will get destroyed, with all the child coordinators with root controllers
return .trigger(SMCApplicationRoute.showApplicationRoot, on: router!)
}
}
}
I think there are better solutions, but I found this to be efective. If the code is not self explaining, just let me know ;)