我正在关注 John Sundell 的帖子以实现导航器模式 (https://www.swiftbysundell.com/posts/navigation-in-swift)。基本思想是,与协调器模式相比,每个 View Controller 都可以简单地调用 navigator.navigate(to: .someScreen)
而无需知道其他 View Controller 。
我的问题是,因为为了构造一个 View Controller 我需要一个导航器,为了构造一个导航器我需要一个导航 Controller ,但是我想让 View Controller 成为导航 Controller 的根,最好的方法是什么以尊重依赖注入(inject)最佳实践的方式解决这种循环依赖?
下面是 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()
}
}
}
View Controller
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
对吗?因为它需要导航器,导航器需要导航 Controller 。如何做到这一点?
最佳答案
我最近也在尝试实现 Sundell 的 Navigator 模式并遇到了同样的循环依赖。我不得不向初始 Navigator 添加一些额外的行为来处理这个奇怪的 Bootstrap 问题。我相信您应用中的后续 Navigators 可以完美地遵循博客的建议。
这是使用 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
}
...
}
关于swift - 如何正确实现导航器模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51228633/