swift - 如何正确实现导航器模式
问题描述
我正在关注 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
正确构建?因为它需要一个导航器,它需要导航控制器。怎么做?
解决方案
我最近也在尝试实现 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
}
...
}
推荐阅读
- flutter - 如何解决这个问题?我没有得到字幕文本
- laravel - 在控制器中使用 foreach 时出现语法错误
- php - 无法使用 PHP 转换日期时间格式
- python - TypeError:('关键字参数不理解:','Dropout')
- php - 我有 2 个输入时间字段,我想如果我在第一个输入字段时间输入 01:00 AM,那么在第二个输入字段时间它应该只有 02:00AM
- php - Laravel foreach 月份从当前月份开始
- php - 使用插件方法覆盖核心块
- google-maps - 谷歌地图折线上的自定义图标图像
- c# - 我必须使用参数化从数据库“员工详细信息”中搜索员工姓名
- php - SQLSTATE [42S22]:未找到列:1054 'where 子句'中的未知列'answers.question_id'