简体   繁体   中英

Swift: How to use Xcoordinator to set a root view controller from child coordinator?

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;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM