【问题标题】:MVVM coordinators and popping a UIViewControllerMVVM 协调器和弹出 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。

其他人是如何解决这个问题的?我确定我不是第一个。

【问题讨论】:

  • 为什么不使用自定义按钮?

标签: ios mvvm uinavigationcontroller


【解决方案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】:

    我想知道您是否已经解决了架构问题,是否愿意分享您的解决方案。我问了一些与您的问题有关的问题hereDaniel 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)
    }
    

    【讨论】:

      猜你喜欢
      • 2018-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-03
      • 2018-05-22
      • 1970-01-01
      • 2023-04-10
      • 1970-01-01
      相关资源
      最近更新 更多