代码之家  ›  专栏  ›  技术社区  ›  Jack Guo

如何正确实现Navigator模式

  •  4
  • Jack Guo  · 技术社区  · 7 年前

    我遵循John Sundell的帖子来实现一个Navigator模式( https://www.swiftbysundell.com/posts/navigation-in-swift )。基本思想是,与协调器模式不同,每个视图控制器只需调用 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 要正确建造,对吗?因为它需要导航器,导航器需要导航控制器。怎么做?

    2 回复  |  直到 7 年前
        1
  •  2
  •   jmk    5 年前

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

    以下是使用JGuo(OP)示例的新初始导航器代码:

    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
  •  1
  •   amirfl    6 年前

    这能解决问题吗?在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
  •  0
  •   Lensflare    6 年前

    我建议在调用'make'函数时,将rootViewController作为参数传递,而不是将其作为LoginViewController工厂的属性:

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