MVVM协调器并弹出一个UIViewController

时间:2017-05-22 10:38:24

标签: ios mvvm uinavigationcontroller

我最近开始使用协调员(例如: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。

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

3 个答案:

答案 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触发另一个操作。父协调员AuthCoordinatorLoginCoordinator数组/集中删除其子childControllers

顺便说一下,为什么你需要将子协调员保留在数组中,然后考虑如何删除它们。我尝试了另一种方法,由视图模型保留的子协调器,一旦取消分配视图模型,协调器也会解除分配。为我工作。

但我个人并不喜欢这么多联系,并且考虑使用单个协调器对象来实现更简单的方法。

答案 1 :(得分:0)

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