Я новичок в RxSwift и пытаюсь сделать как состояния заголовка подход MVVM для ввода-вывода.
Я не могу найти лучший способ сделать следующее.
- Проверка значений phoneNumberTextField при нажатии кнопки submitButton
- Остановить отправку запроса Alamofire, если phoneNumberTextField недействителен, и выдать ошибку на стороне клиента
- Показывать индикатор дисплея, когда происходит загрузка. Это наименее важно сейчас
Несколько замечаний.
- На данный момент ничего не отслеживается с текстом телефонного номера
- Я не хочу отключать кнопку отправки до тех пор, пока форма не станет действительной, как видно из примеров.
Вот мой вид контроллера
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)
}
}
Вот моя модель взгляда.
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
)
}
}
Вот что я придумал.
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
)
}
}
Просмотр изменений контроллера…
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)
}