当使用MVVM模式时如何处理iOS中的导航?

8

我在我的最新iOS应用程序中引入了ViewModels,但遇到了一些问题。想知道这里是否有人能指导我正确的方向。

基本上,导航和错误对话框应该在哪里处理,应该如何处理。

举个例子,目前我有一个注册过程,包括一个RegistrationViewController和一个RegistrationViewModel。当UI中点击注册按钮时,在控制器中调用RegistrationViewModel的register()方法。即从控制器调用viewModel.register()

问题1:应该如何处理导航? 当向web服务的注册调用成功完成后,应用程序应该根据某些业务逻辑导航到屏幕中的一个或多个屏幕。目前我通过调用导航管理类来在ViewModel中处理它。 Navigator.goToSuccessScreen()。这可以吗?我感觉这应该是在控制器内部处理,而不是通过ViewModel处理,但是如果这样做,决定导航到哪里的所有业务逻辑都将在视图控制器中完成。

问题2:如何显示错误对话框? 假设上面的注册调用失败了。UI需要向用户显示UIAlertView。调用Navigator.displayError("some or other error message")可以吗?或者还应该有一种方法将其路由回控制器以自己显示消息?


如果Navigator充当父级角色,那么子VM负责向其发送消息,例如"RegSuccess"、"RegError",并让Navigator决定如何处理这些消息。我猜是这样吧? - zc246
视图按钮调用控制器,然后控制器调用视图模型?控制器通常应该管理导航流程,视图模型提供和改变数据。你的导航器是全局的,但如果你进行依赖注入,那就没问题了。主要是要在分配类的责任方面保持一致。 - Wain
1个回答

1
你可以使用路由枚举并在ViewModel中使其变为可观察。
enum MoviesListViewModelRoute {
    case initial
    case showMovieDetail(title: String, overview: String, posterPlaceholderImage: Data?, posterPath: String?)
}

final class DefaultMoviesListViewModel: MoviesListViewModel {
        
    // MARK: - OUTPUT
    let route: Observable<MoviesListViewModelRoute> = Observable(.initial)
    ...
    func didSelect(item: MoviesListItemViewModel) {
        route.value = .showMovieDetail(title: item.title, 
                                       overview: item.overview, 
                                       posterPlaceholderImage: item.posterImage.value, 
                                       posterPath: item.posterPath)

}

观察来自视图控制器(viewController)的路由值,并在该值更改时呈现它:
final class MoviesListViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        bind(to: viewModel)
        viewModel.viewDidLoad()
    }

    private func bind(to viewModel: MoviesListViewModel) {
        viewModel.route.observe(on: self) { [weak self] in 
            self?.handle($0) 
        }
    }

}

extension MoviesListViewController {
    func handle(_ route: MoviesListViewModelRoute) {
        switch route {
        case .initial: break
        case .showMovieDetail(let title, let overview, let posterPlaceholderImage, let posterPath):
            let vc = moviesListViewControllersFactory.makeMoviesDetailsViewController(title: title, 
                                                                                      overview: overview, 
                                                                                      posterPlaceholderImage: posterPlaceholderImage, 
                                                                                      posterPath: posterPath)
            navigationController?.pushViewController(vc, animated: true)
        }
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接