RxSwift MVVM 在按钮上验证表单提交然后发出 API 请求

RxSwift MVVM Validate Form on Button Submit then Make API Request

我是 RxSwift 的新手,正在尝试使用 MVVM 输入输出方法按照标题所述进行操作。

我想不出执行以下操作的最佳方法。

  1. 点击 submitButton 时验证 phoneNumberTextField 值
  2. 如果 phoneNumberTextField 无效并抛出客户端错误,则停止提交 Alamofire 请求
  3. 加载发生时显示显示指示器。这是目前最不重要的

一些注意事项。

这是我的视图控制器

import UIKit
import RxSwift
import RxCocoa

class SplashViewController: BaseViewController {

    // MARK: – View Variables

    @IBOutlet weak var phoneNumberTextField: UITextField!
    @IBOutlet weak var phoneNumberBackgroundView: UIView!
    @IBOutlet weak var submitButton: BaseButton!
    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var separatorView: UIView!
    @IBOutlet weak var countryCodeButton: UIButton!
    @IBOutlet weak var parentVerticalStackView: UIStackView!

    // MARK: – View Model & RxSwift Setup

    private let disposeBag = DisposeBag()
    private let viewModel: SplashMVVM = SplashMVVM()

    // MARK: – View lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // RxSwift handling
        setupViewModelBinding()
        setupCallbacks()

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        navigationController?.setNavigationBarHidden(true, animated: true)
    }

    // MARK: – RxSwift Handling

    private func setupViewModelBinding() {

        submitButton.rx.controlEvent(.touchUpInside)
            .bind(to: viewModel.input.submit)
            .disposed(by: disposeBag)

    }

    private func setupCallbacks() {

        viewModel.output.success.asObservable()
            .filter { [=11=] != nil }
            .observeOn(MainScheduler())
            .subscribe({ _ in
                self.pushVerifyPhoneNumberViewController()
            })
            .disposed(by: disposeBag)

        viewModel.output.error.asObservable()
            .filter { [=11=] != nil }
            .observeOn(MainScheduler())
            .subscribe({ _ in
                SwiftMessages.show(.error, message: "There was an error. Please try again.")
            })
            .disposed(by: disposeBag)

    }

    // MARK: – Navigation

    func pushVerifyPhoneNumberViewController() {

        let viewController = VerifyPhoneNumberViewController.fromStoryboard("Authentication")

        self.navigationController?.pushViewController(viewController, animated: true)

    }

}

这是我的视图模型。

import Foundation
import RxSwift
import RxCocoa
import Alamofire

final class SplashMVVM: InputOutputModelType {


let input: SplashMVVM.Input
let output: SplashMVVM.Output

var submitSubject = PublishSubject<Void>()

struct Input {
    let submit: AnyObserver<Void>
}

struct Output {
    let success: Observable<VerifyMobilePhone?>
    let error: Observable<Error?>
}    

init() {

    input = Input(submit: submitSubject.asObserver())

    let request = Alamofire.request(VerifyMobileRouter.post("+16306996540")).responseDecodableRx(VerifyMobilePhone.self)

    let requestData = submitSubject.flatMapLatest {
        request
    }

    let success = requestData.map { [=12=].value ?? nil }

    let error = requestData.map { [=12=].error ?? nil }

    output = Output(
        success: success,
        error: error
    )

}

}

这是我的想法。

final class SplashMVVM: InputOutputModelType {

let input: SplashMVVM.Input
let output: SplashMVVM.Output

var submitSubject = PublishSubject<Void>()
var phoneNumberSubject = PublishSubject<String>()

struct Input {
    let phoneNumber: AnyObserver<String>
    let submit: AnyObserver<Void>
}

struct Output {
    let validationError: Observable<String>
    let success: Observable<VerifyMobilePhone>
    let error: Observable<Error>
}

init() {

    input = Input(phoneNumber: phoneNumberSubject.asObserver(), submit: submitSubject.asObserver())

    let request = submitSubject.asObservable().withLatestFrom(phoneNumberSubject.asObservable()).filter {
        [=13=].isValidPhoneNumber(region: "US")
    }.flatMap { number in
        Alamofire.request(VerifyMobileRouter.post(number)).responseDecodableRx(VerifyMobilePhone.self)
    }.share()

    let validationError = submitSubject.asObservable().withLatestFrom(phoneNumberSubject.asObservable()).filter {
        ![=13=].isValidPhoneNumber(region: "US")
    }.map { _ in
        "This phone number is invalid"
    }

    let success = request.filter { [=13=].isSuccess }.map { [=13=].value! }

    let error = request.filter { [=13=].isFailure }.map { [=13=].error! }

    output = Output(
        validationError: validationError,
        success: success,
        error: error
    )

}

}

查看控制器更改...

   private func setupViewModelBinding() {
        submitButton.rx.controlEvent(.touchUpInside).bind(to: viewModel.input.submit).disposed(by: disposeBag)
        phoneNumberTextField.rx.text.orEmpty.bind(to: viewModel.input.phoneNumber).disposed(by: disposeBag)
    }

    private func setupCallbacks() {

        viewModel.output.validationError.bind { string in
            SwiftMessages.show(.error, message: string)
        }.disposed(by: disposeBag)

        viewModel.output.success.bind { verifyMobilePhone in
            self.pushVerifyPhoneNumberViewController()
        }.disposed(by: disposeBag)

        viewModel.output.error.bind { error in
            SwiftMessages.show(.error, message: "There was an error. Please try again.")
        }.disposed(by: disposeBag)

    }

你很接近,你只是缺少 phone 数字文本作为视图模型的输入。

struct SplashInput {
    let phoneNumber: Observable<String>
    let submit: Observable<Void>
}

struct SplashOutput {
    let invalidInput: Observable<Void>
    let success: Observable<VerifyMobilePhone>
    let error: Observable<Error>
}

extension SplashOutput {
    init(_ input: SplashInput) {
        let request: Observable<Event<VerifyMobilePhone>> = input.submit.withLatestFrom(input.phoneNumber)
            .filter { [=10=].isValidPhoneNumber }
            .flatMap { number in
                Alamofire.request(VerifyMobileRouter.post(number)).responseDecodableRx(VerifyMobilePhone.self)
                    .materialize()
            }
            .share()

        invalidInput = input.submit.withLatestFrom(input.phoneNumber)
            .filter { [=10=].isValidPhoneNumber == false }

        success = request
            .map { [=10=].element }
            .filter { [=10=] != nil }
            .map { [=10=]! }

        error = request
            .map { [=10=].error }
            .filter { [=10=] != nil }
            .map { [=10=]! }
    }
}

您的 SplashViewController 会:

override func viewDidLoad() {
    super.viewDidLoad()
    let input = SplashInput(
        phoneNumber: phoneNumberTextField.rx.text.orEmpty.asObservable(),
        submit: submitButton.rx.tap.asObservable()
    )
    let viewModel = SplashOutput(input)
    viewModel.invalidInput
        .bind {
            SwiftMessages.show(.invalid, message: "You entered an invalid number. Please try again.")
       }
        .disposed(by: bag)

    viewModel.success
        .bind { [unowned self] verifyMobilePhone in
            self.pushVerifyPhoneNumberViewController(verifyMobilePhone)
        }
        .disposed(by: bag)

    viewModel.error
        .bind { error in
            SwiftMessages.show(.error(error), message: "There was an error. Please try again.")
        }
 }

(我对你已经写的内容进行了一些改动,但以上内容应该是有道理的。)