MVVM 协调器和弹出 UIViewController
Posted
技术标签:
【中文标题】MVVM 协调器和弹出 UIViewController【英文标题】:MVVM coordinators and popping a UIViewController 【发布时间】:2017-05-22 10:38:23 【问题描述】:我最近开始使用协调器(例如:MVVM with Coordinators and RxSwift)来改进我当前的 MVVM 架构。从 UIViewController 中删除导航相关代码是一个很好的解决方案。
但是我遇到了 1 个特定场景的问题。当 UIViewController 被默认的后退按钮或边缘滑动手势弹出时,就会出现此问题。
使用列表-详细信息界面的快速示例:
列表 UIViewController 由 UINavigationController 内的 ListCoordinator 显示。当一个项目被点击时,ListCoordinator 创建一个 DetailCoordinator,将其注册为子协调器并启动它。 DetailCoordinator 将详细信息 UIViewController 推送到 UINavigationController 上,就像每篇 MVVM-C 博客文章所说明的那样。
每篇 MVVM-C 博客文章都未能说明的是,当 UIViewController 详细信息被默认的后退按钮或边缘滑动手势弹出时会发生什么。
DetailCoordinator 应该负责弹出细节 UIViewController,但是 a) 它不知道后退按钮被点击,并且 b) 弹出是自动发生的。此外,ListCoordinator 无法从其子协调器中删除 DetailCoordinator。
一种解决方案是使用自定义后退按钮,该按钮发出点击信号并将其传递给 DetailCoordinator。另一个可能是使用 UINavigationControllerDelegate。
其他人是如何解决这个问题的?我确定我不是第一个。
【问题讨论】:
为什么不使用自定义按钮? 【参考方案1】:我使用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()
登录协调员
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
从childControllers
数组/集合中删除其子协调器LoginCoordinator
。
顺便说一句,为什么需要将子协调器保留在数组中,然后考虑如何删除它们。我尝试了另一种方法,由视图模型保留的子协调器,一旦视图模型被释放,协调器也被释放。为我工作。
但我个人不喜欢这么多的连接,并考虑使用单个协调器对象来处理所有事情的更简单方法。
【讨论】:
【参考方案2】:我想知道您是否已经解决了架构问题,是否愿意分享您的解决方案。我问了一些与您的问题有关的问题here 和Daniel T. 建议订阅navigationController.rx.willShow
:每当 ViewController 弹出或推送到视图控制器堆栈时,您都会收到事件,因此您需要检查自己是什么样的事件是(流行或推动)。我认为 viewModel / viewController 不应该知道下一个要呈现的故事,所以我认为 viewModel 可以发出一个事件(“显示表格单元格 #n 的详细信息”)并且协调器应该推送或弹出正确的场景(“单元格#n 的详细信息")。这种架构对我来说太高级了,无法编写,所以我最终会遇到很多循环引用/内存泄漏。
【讨论】:
另一种获得通知的方式可能是订阅viewController.rx.sentMessage(#selector(UIViewController.viewWillDisappear(_:)))
,正如NRitH 所建议的那样,但是您会像以前一样收到推送和弹出事件的事件,因为当推送发生时,viewWillDisappear
会调用“呈现”viewController。
我确实尝试了UINavigationControllerDelegate
(willShow
) 解决方案,虽然它适用于 1 个UIViewController
,但对于多个UIViewController
,它不是一个可行的解决方案。最后,我们使用了一个自定义的后退按钮,它通知视图模型,视图模型通知协调器,允许协调器弹出UIViewController
。
在 this 帖子中,RayWenderlich RxSwift Book 的合著者之一 Marin Todorov 解释了如何使用 UINavigationController 呈现 ViewController 以及如何观察呈现的 ViewController 何时结束其工作。在本书的第 4 章中,他深入解释了所有内容(所介绍的 VC 提供了一个从 viewWillDisappear 发出完成事件的可观察对象。当它因其他原因结束工作时也会发生同样的情况,但在这种情况下,它也会发出下一个事件)。 【参考方案3】:
除非我遗漏了什么,否则您可以通过在坐标方法中使用这段代码来解决这个问题。我专门使用 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)
【讨论】:
以上是关于MVVM 协调器和弹出 UIViewController的主要内容,如果未能解决你的问题,请参考以下文章