From 160a0d5f703f13455b1d97f03e7cce52a7ef9c75 Mon Sep 17 00:00:00 2001 From: Tech-Nayuta Date: Fri, 26 Mar 2021 21:01:28 +0900 Subject: [PATCH 1/3] =?UTF-8?q?ViewModel=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/View/LoginViewController.swift | 85 +++++++++--------- .../Sources/ViewModel/LoginViewModel.swift | 87 ++++++++++++++++++- 2 files changed, 130 insertions(+), 42 deletions(-) diff --git a/hack_iOS/Sources/View/LoginViewController.swift b/hack_iOS/Sources/View/LoginViewController.swift index 30330b1..618cd5c 100644 --- a/hack_iOS/Sources/View/LoginViewController.swift +++ b/hack_iOS/Sources/View/LoginViewController.swift @@ -39,13 +39,16 @@ final class LoginViewController: UIViewController, UITextFieldDelegate { } } - private let authRepository: AuthRepository - private let keychainAccessRepository: KeychainAccessRepository private let disposeBag = DisposeBag() + private let viewModel: LoginViewModelType - init(authRepository: AuthRepository = .init(), keychainAccessRepository: KeychainAccessRepository = .init()) { - self.authRepository = authRepository - self.keychainAccessRepository = keychainAccessRepository + init( + authRepository: AuthRepository = .init(), + keychainAccessRepository: KeychainAccessRepository = .init() + ){ + self.viewModel = LoginViewModel( + dependency: .init(authrepository: authRepository, keychainAccessRepository: keychainAccessRepository) + ) super.init(nibName: nil, bundle: nil) } @@ -63,7 +66,7 @@ final class LoginViewController: UIViewController, UITextFieldDelegate { } @objc private func tapLoginButton() { - login() +// login() } @objc private func tapMoveToSignUpViewButton() { @@ -78,39 +81,39 @@ final class LoginViewController: UIViewController, UITextFieldDelegate { return true } - private func login() { - guard - let username = userNameTextField.text, - let password = passwordTextField.text - else { - return - } - - authRepository.login(username: username, password: password) - .subscribe(on: SerialDispatchQueueScheduler(qos: .background)) - .observe(on: MainScheduler.instance) - .subscribe( onSuccess: { [weak self] token in - guard let me = self else { return } - guard let authToken = token["token"] else { return } - me.keychainAccessRepository.save(token: authToken) - let rootVC = ListViewController() - let navVC = UINavigationController(rootViewController: rootVC) - navVC.modalPresentationStyle = .fullScreen - self?.present(navVC, animated: true) - }, - onFailure: { error in - guard let error = error as? APIError else { return } - switch error { - case .decode(let error): - print(error) - case .network(let error): - print(error) - case .unknown(let error): - print(error) - case .noResponse: - print("No Response") - } - }) - .disposed(by: disposeBag) - } +// private func login() { +// guard +// let username = userNameTextField.text, +// let password = passwordTextField.text +// else { +// return +// } +// +// authRepository.login(username: username, password: password) +// .subscribe(on: SerialDispatchQueueScheduler(qos: .background)) +// .observe(on: MainScheduler.instance) +// .subscribe( onSuccess: { [weak self] token in +// guard let me = self else { return } +// guard let authToken = token["token"] else { return } +// me.keychainAccessRepository.save(token: authToken) +// let rootVC = ListViewController() +// let navVC = UINavigationController(rootViewController: rootVC) +// navVC.modalPresentationStyle = .fullScreen +// self?.present(navVC, animated: true) +// }, +// onFailure: { error in +// guard let error = error as? APIError else { return } +// switch error { +// case .decode(let error): +// print(error) +// case .network(let error): +// print(error) +// case .unknown(let error): +// print(error) +// case .noResponse: +// print("No Response") +// } +// }) +// .disposed(by: disposeBag) +// } } diff --git a/hack_iOS/Sources/ViewModel/LoginViewModel.swift b/hack_iOS/Sources/ViewModel/LoginViewModel.swift index e0c3a54..926fd0a 100644 --- a/hack_iOS/Sources/ViewModel/LoginViewModel.swift +++ b/hack_iOS/Sources/ViewModel/LoginViewModel.swift @@ -6,7 +6,92 @@ // import Foundation +import RxSwift +import RxCocoa -class LoginViewModel { +protocol LoginViewModelType { + var input: LoginViewModelInput { get } + var output: LoginViewModelOutput { get } +} + +protocol LoginViewModelInput { + var username: BehaviorRelay { get } + var password: BehaviorRelay { get } + func loginButtonTapped() + func moveToSignUpButtonTapped() +} + +protocol LoginViewModelOutput { + var loginSucceeded: Signal { get } +} + +class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOutput { + struct Dependency { + var authrepository: AuthRepository + var keychainAccessRepository: KeychainAccessRepository + } + + let username = BehaviorRelay(value: "") + let password = BehaviorRelay(value: "") + + private let loginButtonTappedRelay = PublishRelay() + private let moveToSignUpButtonTappedRelay = PublishRelay() + + private let loginSucceededRelay = PublishRelay() + var loginSucceeded: Signal { + loginSucceededRelay.asSignal() + } + + private let dependency: Dependency + private let disposeBag = DisposeBag() + + var input: LoginViewModelInput { self } + var output: LoginViewModelOutput { self } + + init(dependency: Dependency) { + self.dependency = dependency + + let loginEvent = loginButtonTappedRelay.asObservable() + .withLatestFrom(username) + .withLatestFrom(password) { ($0, $1) } + .flatMap { username, password in + dependency.authrepository.login(username: username, password: password) + .asObservable().materialize() + } + .share() + + loginEvent + .flatMap { $0.element.map(Observable.just) ?? .empty() } + .subscribe( Binder(self) { me, token in + guard let authToken = token["token"] else { return } + me.dependency.keychainAccessRepository.save(token: authToken) + let rootVC = ListViewController() + let navVC = UINavigationController(rootViewController: rootVC) + navVC.modalPresentationStyle = .fullScreen + me.loginSucceededRelay.accept(()) + }) + .disposed(by: disposeBag) + + loginEvent + .flatMap { $0.error.map(Observable.just) ?? .empty() } + .subscribe( Binder(self) { _, error in + guard let error = error as? APIError else { return } + switch error { + case .decode(let error): + print(error) + case .network(let error): + print(error) + case .unknown(let error): + print(error) + case .noResponse: + print("No Response") + } + }) + .disposed(by: disposeBag) + + + } + func loginButtonTapped() { loginButtonTappedRelay.accept(()) } + func moveToSignUpButtonTapped() { moveToSignUpButtonTappedRelay.accept(()) } } From f82d5424afeabbfbb34ed6dcba83b473613baad2 Mon Sep 17 00:00:00 2001 From: Tech-Nayuta Date: Fri, 26 Mar 2021 23:21:40 +0900 Subject: [PATCH 2/3] =?UTF-8?q?ViewController=E3=82=92Rx+MVVM=E9=A2=A8?= =?UTF-8?q?=E3=81=AB=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/View/LoginViewController.swift | 88 ++++++++----------- .../Sources/ViewModel/LoginViewModel.swift | 11 ++- 2 files changed, 44 insertions(+), 55 deletions(-) diff --git a/hack_iOS/Sources/View/LoginViewController.swift b/hack_iOS/Sources/View/LoginViewController.swift index 618cd5c..0285903 100644 --- a/hack_iOS/Sources/View/LoginViewController.swift +++ b/hack_iOS/Sources/View/LoginViewController.swift @@ -28,14 +28,12 @@ final class LoginViewController: UIViewController, UITextFieldDelegate { @IBOutlet private weak var loginButton: UIButton! { didSet { loginButton.setTitle("Login", for: .normal) - loginButton.addTarget(self, action: #selector(tapLoginButton), for: .touchUpInside) } } @IBOutlet private weak var moveToSignUpViewButton: UIButton! { didSet { moveToSignUpViewButton.setTitle("SignupView", for: .normal) - moveToSignUpViewButton.addTarget(self, action: #selector(tapMoveToSignUpViewButton), for: .touchUpInside) } } @@ -59,61 +57,53 @@ final class LoginViewController: UIViewController, UITextFieldDelegate { override func viewDidLoad() { super.viewDidLoad() + bindViewModel() } - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - self.view.endEditing(true) - } - - @objc private func tapLoginButton() { -// login() + private func bindViewModel() { + + //input + userNameTextField.rx.text.orEmpty.asObservable() + .bind(to: viewModel.input.username) + .disposed(by: disposeBag) + + passwordTextField.rx.text.orEmpty.asObservable() + .bind(to: viewModel.input.password) + .disposed(by: disposeBag) + + loginButton.rx.tap + .bind(to: Binder(self) { me, _ in + me.viewModel.input.loginButtonTapped() + }) + .disposed(by: disposeBag) + + //output + viewModel.output.loginSucceeded.asObservable() + .bind(to: Binder(self) { me, _ in + let rootVC = ListViewController() + let navVC = UINavigationController(rootViewController: rootVC) + navVC.modalPresentationStyle = .fullScreen + me.present(navVC, animated: true) + }) + .disposed(by: disposeBag) + + //サインアップ画面へ遷移 + moveToSignUpViewButton.rx.tap.asDriver() + .drive(onNext: { [weak self] in + let rootVC = SignUpViewController() + let navVC = UINavigationController(rootViewController: rootVC) + navVC.modalPresentationStyle = .fullScreen + self?.present(navVC, animated: true) + }) + .disposed(by: disposeBag) } - @objc private func tapMoveToSignUpViewButton() { - let rootVC = SignUpViewController() - let navVC = UINavigationController(rootViewController: rootVC) - navVC.modalPresentationStyle = .fullScreen - present(navVC, animated: true) + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.view.endEditing(true) } func textFieldShouldReturn(_ textField: UITextField) -> Bool { userNameTextField.resignFirstResponder() return true } - -// private func login() { -// guard -// let username = userNameTextField.text, -// let password = passwordTextField.text -// else { -// return -// } -// -// authRepository.login(username: username, password: password) -// .subscribe(on: SerialDispatchQueueScheduler(qos: .background)) -// .observe(on: MainScheduler.instance) -// .subscribe( onSuccess: { [weak self] token in -// guard let me = self else { return } -// guard let authToken = token["token"] else { return } -// me.keychainAccessRepository.save(token: authToken) -// let rootVC = ListViewController() -// let navVC = UINavigationController(rootViewController: rootVC) -// navVC.modalPresentationStyle = .fullScreen -// self?.present(navVC, animated: true) -// }, -// onFailure: { error in -// guard let error = error as? APIError else { return } -// switch error { -// case .decode(let error): -// print(error) -// case .network(let error): -// print(error) -// case .unknown(let error): -// print(error) -// case .noResponse: -// print("No Response") -// } -// }) -// .disposed(by: disposeBag) -// } } diff --git a/hack_iOS/Sources/ViewModel/LoginViewModel.swift b/hack_iOS/Sources/ViewModel/LoginViewModel.swift index 926fd0a..bebd558 100644 --- a/hack_iOS/Sources/ViewModel/LoginViewModel.swift +++ b/hack_iOS/Sources/ViewModel/LoginViewModel.swift @@ -26,6 +26,7 @@ protocol LoginViewModelOutput { } class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOutput { + struct Dependency { var authrepository: AuthRepository var keychainAccessRepository: KeychainAccessRepository @@ -53,7 +54,10 @@ class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOut let loginEvent = loginButtonTappedRelay.asObservable() .withLatestFrom(username) - .withLatestFrom(password) { ($0, $1) } + .withLatestFrom(password) { ($0, $1) } // optionalの場合はエラーを吐く.... + .filter { username, password in + !username.isEmpty && !password.isEmpty + } .flatMap { username, password in dependency.authrepository.login(username: username, password: password) .asObservable().materialize() @@ -65,9 +69,6 @@ class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOut .subscribe( Binder(self) { me, token in guard let authToken = token["token"] else { return } me.dependency.keychainAccessRepository.save(token: authToken) - let rootVC = ListViewController() - let navVC = UINavigationController(rootViewController: rootVC) - navVC.modalPresentationStyle = .fullScreen me.loginSucceededRelay.accept(()) }) .disposed(by: disposeBag) @@ -88,8 +89,6 @@ class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOut } }) .disposed(by: disposeBag) - - } func loginButtonTapped() { loginButtonTappedRelay.accept(()) } From 2dcde8155a6621b95330c28404aaf2d8a8a47fb4 Mon Sep 17 00:00:00 2001 From: Tech-Nayuta Date: Fri, 26 Mar 2021 23:25:24 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E5=BE=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hack_iOS/Sources/ViewModel/LoginViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack_iOS/Sources/ViewModel/LoginViewModel.swift b/hack_iOS/Sources/ViewModel/LoginViewModel.swift index bebd558..fe6c10c 100644 --- a/hack_iOS/Sources/ViewModel/LoginViewModel.swift +++ b/hack_iOS/Sources/ViewModel/LoginViewModel.swift @@ -25,7 +25,7 @@ protocol LoginViewModelOutput { var loginSucceeded: Signal { get } } -class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOutput { +final class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOutput { struct Dependency { var authrepository: AuthRepository