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 的另一个操作。父协调器AuthCoordinatorchildControllers 数组/集合中删除其子协调器LoginCoordinator

顺便说一句,为什么需要将子协调器保留在数组中,然后考虑如何删除它们。我尝试了另一种方法,由视图模型保留的子协调器,一旦视图模型被释放,协调器也被释放。为我工作。

但我个人不喜欢这么多的连接,并考虑使用单个协调器对象来处理所有事情的更简单方法。

【讨论】:

【参考方案2】:

我想知道您是否已经解决了架构问题,是否愿意分享您的解决方案。我问了一些与您的问题有关的问题here 和Daniel T. 建议订阅navigationController.rx.willShow:每当 ViewController 弹出或推送到视图控制器堆栈时,您都会收到事件,因此您需要检查自己是什么样的事件是(流行或推动)。我认为 viewModel / viewController 不应该知道下一个要呈现的故事,所以我认为 viewModel 可以发出一个事件(“显示表格单元格 #n 的详细信息”)并且协调器应该推送或弹出正确的场景(“单元格#n 的详细信息")。这种架构对我来说太高级了,无法编写,所以我最终会遇到很多循环引用/内存泄漏。

【讨论】:

另一种获得通知的方式可能是订阅viewController.rx.sentMessage(#selector(UIViewController.vie‌​wWillDisappear(_:))),正如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的主要内容,如果未能解决你的问题,请参考以下文章

UIViewController 推送和弹出中的问题

使窗口弹出和弹出屏幕边缘

python 异常和弹出框

如何添加模糊背景和弹出视图?

java 堆栈 - 推送和弹出操作

golang 可以推送和弹出的intlist