我最近开始使用协调员(例如:MVVM with Coordinators and RxSwift)来改进我当前的MVVM架构。从UIViewController中删除导航相关代码是一个不错的解决方案。
但是我遇到了一个特定情况的问题。当UIViewController被默认的后退按钮或边缘滑动手势弹出时,问题就出现了。
使用list-detail界面的快速示例:
列表UIViewController由UINavigationController内的ListCoordinator显示。当点击一个项目时,ListCoordinator创建一个DetailCoordinator,将其注册为子协调器并启动它。 DetailCoordinator将细节UIViewController推送到UINavigationController,就像每个MVVM-C博客文章所示。
每个MVVM-C博客文章都无法说明当默认后退按钮或边缘滑动手势弹出细节UIViewController时会发生什么。
DetailCoordinator应该负责弹出细节UIViewController,但是a)它不知道后面的按钮被点击了,b)pop会自动发生。此外,ListCoordinator无法从其子协调员中删除DetailCoordinator。
一种解决方案是使用自定义后退按钮,该按钮指示点击并将其传递给DetailCoordinator。另一个可能是使用UINavigationControllerDelegate。
其他人如何解决这个问题?我确定我不是第一个。
答案 0 :(得分:3)
我使用Action进行协调员之间以及协调器和视图控制器之间的通信。
<强> AuthCoordinator 强>
final class AuthCoordinator: Coordinator {
func startLogin(viewModel: LoginViewModel) {
let loginCoordinator = LoginCoordinator(navigationController: navigationController)
loginCoordinator.start(viewModel: viewModel)
viewModel.coordinator = loginCoordinator
// This is where a child coordinator removed
loginCoordinator.stopAction = CocoaAction { [unowned self] _ in
if let index = self.childCoordinators.index(where: { type(of: $0) == LoginCoordinator.self }) {
self.childCoordinators.remove(at: index)
}
return .empty()
}
}
}
<强> LoginCoordinator 强>
final class LoginCoordinator: Coordinator {
var stopAction: CocoaAction?
func start(viewModel: LoginViewModel) {
let loginViewController = UIStoryboard.auth.instantiate(LoginViewController.self)
loginViewController.setViewModel(viewModel: viewModel)
navigationController?.pushViewController(loginViewController, animated: true)
loginViewController.popAction = CocoaAction { [unowned self] _ in
self.stopAction?.execute(Void())
return .empty()
}
}
}
<强> LoginViewController 强>
class LoginViewController: UIViewController {
var popAction: CocoaAction?
override func didMove(toParentViewController parent: UIViewController?) {
super.didMove(toParentViewController: parent)
if parent == nil { // parent is `nil` when the vc is popped
popAction?.execute(Void())
}
}
}
因此LoginViewController
会在弹出时执行操作。它的协调员LoginCoordinator
知道该视图已弹出。它从其父协调员AuthCoordinator
触发另一个操作。父协调员AuthCoordinator
从LoginCoordinator
数组/集中删除其子childControllers
。
但我个人并不喜欢这么多联系,并且考虑使用单个协调器对象来实现更简单的方法。
答案 1 :(得分:0)
我想知道您是否已经解决了您的架构问题,以及您是否愿意分享您的解决方案。我问了一些与你的问题相关的问题here和Daniel T.建议订阅navigationController.rx.willShow
:只要弹出ViewController或将其推送到视图控制器堆栈,就会返回事件,因此您需要检查你自己是什么样的事件(流行音乐或推动)。我认为viewModel / viewController不应该知道要呈现的下一个故事的任何内容,所以我认为viewModel可以发出一个事件(&#34;显示表格单元格#n&#34;的详细信息),协调员应该推送或弹出正确的场景(&#34;单元格#n&#34的细节;)。这种架构对我来说太先进了,所以我最终得到了很多循环引用/内存泄漏。
答案 2 :(得分:0)
除非我缺少任何内容,否则可以通过在坐标方法中使用这段代码来解决此问题。我专门使用didShow代替willShow(在另一个答案中建议使用),以进行边缘轻扫手势。
if let topViewController = navigationController?.topViewController {
navigationController?.rx
.didShow
.filter { $0.viewController == topViewController }
.first()
.subscribe(onSuccess: { [weak self] _ in
// remove child coordinator
})
.disposed(by: disposeBag)
}