RxSwift MVVM 在按钮上验证表单提交然后发出 API 请求
RxSwift MVVM Validate Form on Button Submit then Make API Request
我是 RxSwift 的新手,正在尝试使用 MVVM 输入输出方法按照标题所述进行操作。
我想不出执行以下操作的最佳方法。
- 点击 submitButton 时验证 phoneNumberTextField 值
- 如果 phoneNumberTextField 无效并抛出客户端错误,则停止提交 Alamofire 请求
- 加载发生时显示显示指示器。这是目前最不重要的
一些注意事项。
- 目前没有任何内容跟踪 phone 号码文本
- 在整个示例中看到的表单有效之前,我不想禁用提交按钮。
这是我的视图控制器
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.")
}
}
(我对你已经写的内容进行了一些改动,但以上内容应该是有道理的。)
我是 RxSwift 的新手,正在尝试使用 MVVM 输入输出方法按照标题所述进行操作。
我想不出执行以下操作的最佳方法。
- 点击 submitButton 时验证 phoneNumberTextField 值
- 如果 phoneNumberTextField 无效并抛出客户端错误,则停止提交 Alamofire 请求
- 加载发生时显示显示指示器。这是目前最不重要的
一些注意事项。
- 目前没有任何内容跟踪 phone 号码文本
- 在整个示例中看到的表单有效之前,我不想禁用提交按钮。
这是我的视图控制器
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.")
}
}
(我对你已经写的内容进行了一些改动,但以上内容应该是有道理的。)