简体   繁体   English

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

[英]RxSwift MVVM Validate Form on Button Submit then Make API Request

I'm new to RxSwift and attempting to do as the title states with an MVVM input output approach. 我是RxSwift的新手,并尝试使用MVVM输入输出方法作为标题说明。

I can't figure out the best approach to do the following. 我不知道执行以下操作的最佳方法。

  1. Validate the phoneNumberTextField values when submitButton is tapped 轻按SubmitButton时验证phoneNumberTextField的值
  2. Stop the Alamofire Request from being submitted if phoneNumberTextField is invalid and throw a client side error 如果phoneNumberTextField无效,则停止提交Alamofire请求,并抛出客户端错误
  3. Show a display indicator when loading takes place. 加载时显示显示指示器。 This is the least important right now 现在这是最不重要的

A few things to note. 需要注意的几件事。

  • There is nothing tracking the phone number text at the moment 目前没有追踪电话号码文字的内容
  • I do not want to disable the submit button until the form is valid as seen in examples all over. 我不想禁用提交按钮,直到表单有效为止(如示例所示)。

Here is my view controller 这是我的视图控制器

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 { $0 != nil }
            .observeOn(MainScheduler())
            .subscribe({ _ in
                self.pushVerifyPhoneNumberViewController()
            })
            .disposed(by: disposeBag)

        viewModel.output.error.asObservable()
            .filter { $0 != 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)

    }

}

Here is my view model. 这是我的视图模型。

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 { $0.value ?? nil }

    let error = requestData.map { $0.error ?? nil }

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

}

} }

Here is what I came up with. 这是我想出的。

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 {
        $0.isValidPhoneNumber(region: "US")
    }.flatMap { number in
        Alamofire.request(VerifyMobileRouter.post(number)).responseDecodableRx(VerifyMobilePhone.self)
    }.share()

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

    let success = request.filter { $0.isSuccess }.map { $0.value! }

    let error = request.filter { $0.isFailure }.map { $0.error! }

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

}

} }

View controller changes… 视图控制器更改…

   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)

    }

You are close, you're just missing the phone number text as input into your view model. 亲密无间,您只是缺少电话号码文字作为视图模型的输入。

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 { $0.isValidPhoneNumber }
            .flatMap { number in
                Alamofire.request(VerifyMobileRouter.post(number)).responseDecodableRx(VerifyMobilePhone.self)
                    .materialize()
            }
            .share()

        invalidInput = input.submit.withLatestFrom(input.phoneNumber)
            .filter { $0.isValidPhoneNumber == false }

        success = request
            .map { $0.element }
            .filter { $0 != nil }
            .map { $0! }

        error = request
            .map { $0.error }
            .filter { $0 != nil }
            .map { $0! }
    }
}

Your SplashViewController would have: 您的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.")
        }
 }

(I took some liberties with what you already have written, but the above should make sense.) (我对您已经写的内容有一些自由,但以上所述应该是合理的。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM