使用 RxSwift 从 ViewController 向 ViewModel 传递值

Posted

技术标签:

【中文标题】使用 RxSwift 从 ViewController 向 ViewModel 传递值【英文标题】:Passing value to ViewModel from ViewController w/ RxSwift 【发布时间】:2019-06-13 12:37:24 【问题描述】:

我在一个应用中有一个场景,我被要求对其进行更改。

我对@9​​87654321@不是很熟悉,所以如果这很明显,请原谅我。

我的视图控制器呈现一个包含登录表单的WKWebView。当用户完成表单时,会发回我当前在WKNavigationDelegate 中打印出来的属性。

我想做的是将该属性传递给我的oauthService,它作为我的ViewModel 中的依赖项存在。

我可以在模型中创建一个方法,例如

  func passPropToServie(_ prop: String) 
        // do something
    

只是从视图控制器调用它,但我不确定这是否正确,或者'rxswift'的执行方式是什么。

再次抱歉,这是一个我正在使用的应用程序,所以我仍在接受原始代码。

登录协调员

import UIKit
import RxCocoa
import RxSwift

class LoginCoordinator: BaseCoordinator<()> 
    typealias Dependencies = HasOAuthService

    private let window: UIWindow
    private let dependencies: Dependencies

    init(window: UIWindow, dependencies: Dependencies) 
        self.window = window
        self.dependencies = dependencies
    

    override func start() -> Observable<()> 
        let viewController = LoginViewController()
        let avm: Attachable<LoginViewModel> = .detached(dependencies)
        let viewModel = viewController.attach(wrapper: avm)

        viewModel.loginURL.drive(onNext:  login in
            viewController.handle(login?.url)
        ).disposed(by: viewController.disposeBag)

        window.rootViewController = viewController
        window.makeKeyAndVisible()

        return viewModel.isLoggedIn
            .asObservable()
            .filter  $0 
            .map  _ in return 
    

LoginViewController

import UIKit
import WebKit
import RxCocoa
import RxSwift

class LoginViewController: UIViewController, ViewModelAttaching 

    let disposeBag = DisposeBag()

    var viewModel: Attachable<LoginViewModel>!
    var bindings: LoginViewModel.Bindings 
        return LoginViewModel.Bindings()
    

    private var requestURL: URL?

    lazy var webView: WKWebView = 
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.dataDetectorTypes = [.all]
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.navigationDelegate = self
        webView.allowsBackForwardNavigationGestures = false
        return webView
    ()

    override func viewDidLoad() 
        super.viewDidLoad()
    

    override func loadView() 
        view = webView
    

    func bind(viewModel: LoginViewModel) -> LoginViewModel 

        return viewModel
    

    func handle(_ url: URL?) 
        guard let url = url else  return 
        requestURL = url
        webView.load(URLRequest(url: url))
    


extension LoginViewController: WKNavigationDelegate 
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) 
        if let url = navigationAction.request.url, url.scheme == "homedev", url.valueOf("code") != nil 
            print(url)
        
        decisionHandler(.allow)
    

LoginViewModel

import RxSwift
import RxCocoa

final class LoginViewModel: ViewModelType 

    typealias Dependency = HasOAuthService

    let isLoggedIn: Driver<Bool>
    let loginURL: Driver<URLComponents?>

    struct Bindings  

    init(dependency: Dependency, bindings: Bindings) 

        isLoggedIn = dependency.oauthService.currentUser
            .map  user in return user == true 
            .asDriver(onErrorJustReturn: false)

        loginURL = dependency.oauthService.loginURL
            .map  $0 
            .asDriver(onErrorJustReturn: nil)
    

【问题讨论】:

如果您获得了对模型的引用,那么只需在其上调用方法就可以了。如果您想根据oauth 的结果更新您的 UI,那么您可以使用 RxSwift/RxCocoa/RxRelay。对于oauth 本身,RxSwift 对 IMO 没有多大用处。不过,PromiseKit 可能会让事情变得更容易。就我个人而言,我会用 Promise 封装oauth 请求,并根据结果更改我的 UI 绑定/监听的 RxSwift 变量。 谢谢,感谢您抽出宝贵时间回答:) 【参考方案1】:

这完全取决于你想去兔子洞多远。 Rx 旨在取代我们通常将数据推送到对象中的所有不同方式。因此,如果您将其发挥到极致,您将不会有任何回调闭包、@IBActions 或委托;并且您不会分配给var,也不会使用数据调用函数,只是这样您就可以在另一个类中使用数据。例如,有一些包装器可以让您消除 LoginViewController 中的委托功能。

我猜Bindings 结构是用于视图模型的输入,如果是这种情况,那么我可能会在绑定中添加一个 Observable 并在视图控制器中添加一个发布主题。在发布主题上调用 onNext 将通过绑定在视图模型中被拾取。

您可能会从阅读这篇文章中受益:Integrating RxSwift Into Your Brain and Code Base

【讨论】:

非常感谢,您对Bindings 的看法是正确的,我非常感谢您的反馈。很有帮助。 你的问题感觉堆栈溢出有点大。您可能会从加入我们的 slack 社区中受益:slack.rxswift.org 嗨@Daniel T.,我想学习 RxSwift 基础知识和多个视图控制器中的数据传输。你有任何教程吗?你能指导我吗? 我的 RxEarthquake 示例 (github.com/danielt1263/RxEarthquake) 在两个视图控制器之间共享一个地震模型。我还刚刚完成了一篇文章 (medium.com/@danielt1263/viper-rxswift-ified-1ec3ae8ab9a6),它在一个视图控制器中添加了一个 Todo,然后在另一个视图控制器中显示它。

以上是关于使用 RxSwift 从 ViewController 向 ViewModel 传递值的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 rxswift 从数组中发出单个值?

如何从使用 RxSwift 返回 Observable 的服务中获取值

使用 RxSwift 从 ViewController 向 ViewModel 传递值

使用 rxSwift 中的 tableView 单元将数据从视图模型传递到视图控制器

可从多个 BehaviorRelay RxSwift 观察到

如何使用 RxSwift 处理应用程序状态