如何正确实现导航器模式

时间:2018-07-08 03:35:35

标签: swift dependency-injection factory circular-dependency

我正在关注John Sundell的帖子,以实现导航器模式(https://www.swiftbysundell.com/posts/navigation-in-swift)。基本思想是,与Coordinator模式相反,每个视图控制器可以简单地调用navigator.navigate(to: .someScreen),而不必了解其他视图控制器。

我的问题是,由于要构造视图控制器,我需要一个导航器,要构造导航器,我需要一个导航控制器,但是我想使视图控制器成为导航控制器的根,所以最好的方法是什么以尊重依赖注入最佳实践的方式解决这种循环依赖?

下面是由Sundell演示的Navigator模式的想法

导航器

protocol Navigator {
    associatedtype Destination    
    func navigate(to destination: Destination)
}

class LoginNavigator: Navigator {
    enum Destination {
        case loginCompleted(user: User)
        case signup
    }

    private weak var navigationController: UINavigationController?
    private let viewControllerFactory: LoginViewControllerFactory

    init(navigationController: UINavigationController,
         viewControllerFactory: LoginViewControllerFactory) {
        self.navigationController = navigationController
        self.viewControllerFactory = viewControllerFactory
    }

    func navigate(to destination: Destination) {
        let viewController = makeViewController(for: destination)
        navigationController?.pushViewController(viewController, animated: true)
    }

    private func makeViewController(for destination: Destination) -> UIViewController {
        switch destination {
        case .loginCompleted(let user):
            return viewControllerFactory.makeWelcomeViewController(forUser: user)
        case .signup:
            return viewControllerFactory.makeSignUpViewController()
        }
    }
}

视图控制器

class LoginViewController: UIViewController {
    private let navigator: LoginNavigator

    init(navigator: LoginNavigator) {
        self.navigator = navigator
        super.init(nibName: nil, bundle: nil)
    }

    private func handleLoginButtonTap() {
        navigator.navigate(to: .loginCompleted(user: user))
    }

    private func handleSignUpButtonTap() {
        navigator.navigate(to: .signup)
    }
}

现在在AppDelegate中,我想做

let factory = LoginViewControllerFactory()
let loginViewController = factory.makeLoginViewController()
let rootNavigationController = UINavigationController(rootViewController: loginViewController)
window?.rootViewController = rootNavigationController

但是我必须以某种方式将rootNavigationController传递到factory中,以便正确构建loginViewController?因为它需要一个导航器,所以需要导航控制器。该怎么做?

3 个答案:

答案 0 :(得分:2)

我最近还试图实现Sundell的Navigator模式,并遇到了同样的循环依赖。我必须在初始导航器中添加一些其他行为才能处理此奇怪的引导问题。我相信您的应用中的后续Navigator可以完美地遵循博客的建议。

这是使用JGuo(OP)示例的新的初始Navigator代码:

confirmPDF.blade.php

<html>     
.
.
.
<div class="container">
    @include('showPDF') //it's the file with the code that genarete the PDF
</div>
<div class="foot-buttons">
...
</div>
</body>

</html>

现在在AppDelegate中:

class LoginNavigator: Navigator {
    enum Destination {
        case loginCompleted(user: User)
        case signup 
    }

    private var navigationController: UINavigationController? 
    // This ^ doesn't need to be weak, as we will instantiate it here.

    private let viewControllerFactory: LoginViewControllerFactory

    // New:
    private let appWindow: UIWindow? 
    private var isBootstrapped = false 
    // We will use this ^ to know whether or not to set the root VC

    init(appWindow: UIWindow?, // Pass in your app's UIWindow from the AppDelegate
         viewControllerFactory: LoginViewControllerFactory) {
        self.appWindow = appWindow
        self.viewControllerFactory = viewControllerFactory
    }

    func navigate(to destination: Destination) {
        let viewController = makeViewController(for: destination)

        // We'll either call bootstrap or push depending on 
        // if this is the first time we've launched the app, indicated by isBootstrapped
        if self.isBootstrapped {
            self.pushViewController(viewController)
        } else {
            bootstrap(rootViewController: viewController)
            self.isBootstrapped = true
        }
    }

    private func makeViewController(for destination: Destination) -> UIViewController {
        switch destination {
        case .loginCompleted(let user):
            return viewControllerFactory.makeWelcomeViewController(forUser: user)
        case .signup:
            return viewControllerFactory.makeSignUpViewController()
        }
    }

    // Add these two new helper functions below:
    private func bootstrap(rootViewController: UIViewController) {
        self.navigationController = UINavigationController(rootViewController: rootViewController)
        self.appWindow?.rootViewController = self.navigationController
    }

    private func pushViewController(_ viewController: UIViewController) {
        // Setup navigation look & feel appropriate to your app design...
        navigationController?.setNavigationBarHidden(true, animated: false) 
        self.navigationController?.pushViewController(viewController, animated: true)
    }
}

答案 1 :(得分:0)

这解决了吗?在AppDelegate中:

let factory = LoginViewControllerFactory()
let navController = UINavigationController()

let loginNavigator = LoginNavigator(navigationController: navController, viewControllerFactory: factory)

loginNavigator.navigate(to: .signup) // The example doesn't have a .login Destination, but it can easily be added to the factory, so using .signup instead

window?.rootViewController = navController

答案 2 :(得分:0)

我建议不要将rootViewController作为LoginViewControllerFactory的属性,而建议在调用“ make”函数时将其作为参数传递:

return viewControllerFactory.makeWelcomeViewController(forUser: user, with: rootViewController)