【问题标题】:How to properly implement Navigator pattern如何正确实现导航器模式
【发布时间】:2018-07-08 03:35:35
【问题描述】:

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

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

下面是 Sundell 说明的导航器模式的想法

导航器

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 对吗?因为它需要一个导航器,它需要导航控制器。该怎么做?

【问题讨论】:

    标签: swift dependency-injection factory circular-dependency


    【解决方案1】:

    我最近也在尝试实现 Sundell 的导航器模式并遇到了同样的循环依赖。我不得不在初始 Navigator 中添加一些额外的行为来处理这个奇怪的引导问题。我相信您应用中的后续导航器可以完美地遵循博客的建议。

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

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

    现在在 AppDelegate 内部:

    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
    
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
            window = UIWindow(frame: UIScreen.main.bounds)
            let factory = LoginViewControllerFactory()
            let loginViewController = factory.makeLoginViewController()
            loginViewController.navigate(to: .signup) // <- Ideally we wouldn't need to signup on app launch always, but this is the basic idea.
            window?.makeKeyAndVisible()
    
            return true
        }
    ...
    }
    

    【讨论】:

      【解决方案2】:

      这能解决吗?在 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
      

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-02-09
          • 1970-01-01
          • 2023-04-02
          • 1970-01-01
          • 2023-01-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多