-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[refactor] 준비 정보 입력 뷰 Rx 리팩토링 #405
base: suyeon
Are you sure you want to change the base?
Changes from all commits
a5c6104
e73e46c
cb7b5fd
2b75a1c
2cf89fa
e414948
69502fe
e8f7def
a8aa2dc
2d4fd59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,13 +7,19 @@ | |
|
||
import UIKit | ||
|
||
import RxCocoa | ||
import RxSwift | ||
|
||
final class SetReadyInfoViewController: BaseViewController { | ||
|
||
|
||
// MARK: - Property | ||
|
||
private let rootView = SetReadyInfoView() | ||
|
||
private let viewModel: SetReadyInfoViewModel | ||
private let viewWillAppearRelay = PublishRelay<Void>() | ||
private let disposeBag = DisposeBag() | ||
|
||
|
||
// MARK: - Initializer | ||
|
@@ -31,108 +37,152 @@ final class SetReadyInfoViewController: BaseViewController { | |
// MARK: - LifeCycle | ||
|
||
override func loadView() { | ||
self.view = rootView | ||
view = rootView | ||
} | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
view.backgroundColor = .white | ||
|
||
setupNavigationBarBackButton() | ||
setupNavigationBarTitle(with: "준비 정보 입력하기") | ||
|
||
setupBinding() | ||
setupTapGesture() | ||
setupTextField() | ||
bindViewModel() | ||
} | ||
|
||
override func viewWillAppear(_ animated: Bool) { | ||
super.viewWillAppear(animated) | ||
|
||
navigationController?.isNavigationBarHidden = false | ||
viewWillAppearRelay.accept(()) | ||
} | ||
|
||
override func setupView() { | ||
setupNavigationBarBackButton() | ||
setupNavigationBarTitle(with: "준비 정보 입력하기") | ||
} | ||
|
||
override func setupDelegate() { | ||
setTextFieldDelegate() | ||
} | ||
|
||
override func setupAction() { | ||
rootView.readyHourTextField.addTarget( | ||
self, | ||
action: #selector(textFieldDidChange), | ||
for: .editingChanged | ||
) | ||
rootView.readyMinuteTextField.addTarget( | ||
self, | ||
action: #selector(textFieldDidChange), | ||
for: .editingChanged | ||
) | ||
rootView.moveHourTextField.addTarget( | ||
self, | ||
action: #selector(textFieldDidChange), | ||
for: .editingChanged | ||
) | ||
rootView.moveMinuteTextField.addTarget( | ||
self, | ||
action: #selector(textFieldDidChange), | ||
for: .editingChanged | ||
) | ||
rootView.doneButton.addTarget( | ||
self, | ||
action: #selector(doneButtonDidTap), | ||
for: .touchUpInside | ||
) | ||
setupTextField(textField: rootView.readyHourTextField) | ||
setupTextField(textField: rootView.readyMinuteTextField) | ||
setupTextField(textField: rootView.moveHourTextField) | ||
setupTextField(textField: rootView.moveMinuteTextField) | ||
|
||
let tapGesture = UITapGestureRecognizer() | ||
view.addGestureRecognizer(tapGesture) | ||
tapGesture.rx.event | ||
.subscribe(with: self) { owner, _ in | ||
owner.view.endEditing(true) | ||
} | ||
.disposed(by: disposeBag) | ||
Comment on lines
+72
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} | ||
|
||
@objc | ||
private func textFieldDidChange(_ textField: UITextField) { | ||
let text = textField.text ?? "" | ||
viewModel.updateTime(textField: textField.accessibilityIdentifier ?? "", time: text) | ||
viewModel.checkValid( | ||
readyHourText: rootView.readyHourTextField.text ?? "", | ||
readyMinuteText: rootView.readyMinuteTextField.text ?? "", | ||
moveHourText: rootView.moveHourTextField.text ?? "", | ||
moveMinuteText: rootView.moveMinuteTextField.text ?? "" | ||
private func bindViewModel() { | ||
let input = SetReadyInfoViewModel.Input( | ||
viewWillAppear: viewWillAppearRelay, | ||
readyHourText: rootView.readyHourTextField.rx.text.orEmpty.asObservable(), | ||
readyMinuteText: rootView.readyMinuteTextField.rx.text.orEmpty.asObservable(), | ||
moveHourText: rootView.moveHourTextField.rx.text.orEmpty.asObservable(), | ||
moveMinuteText: rootView.moveMinuteTextField.rx.text.orEmpty.asObservable(), | ||
doneButtonDidTap: rootView.doneButton.rx.tap.asObservable() | ||
) | ||
|
||
let output = viewModel.transform(input: input, disposeBag: disposeBag) | ||
|
||
output.readyHourText | ||
.drive(with: self) { owner, text in | ||
owner.rootView.readyHourTextField.text = text | ||
} | ||
.disposed(by: disposeBag) | ||
|
||
output.readyMinuteText | ||
.drive(with: self) { owner, text in | ||
owner.rootView.readyMinuteTextField.text = text | ||
} | ||
.disposed(by: disposeBag) | ||
|
||
output.moveHourText | ||
.drive(with: self) { owner, text in | ||
owner.rootView.moveHourTextField.text = text | ||
} | ||
.disposed(by: disposeBag) | ||
|
||
output.moveMinuteText | ||
.drive(with: self) { owner, text in | ||
owner.rootView.moveMinuteTextField.text = text | ||
} | ||
.disposed(by: disposeBag) | ||
Comment on lines
+93
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 근데 |
||
|
||
output.errMessage | ||
.drive(with: self) { owner, err in | ||
owner.showToast(err) | ||
} | ||
Comment on lines
+118
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 가능하면 축약형이 아닌 |
||
.disposed(by: disposeBag) | ||
|
||
output.doneButtonIsEnabled | ||
.drive(with: self) { owner, isEnabled in | ||
owner.rootView.doneButton.backgroundColor = isEnabled ? .maincolor : .gray2 | ||
owner.rootView.doneButton.isEnabled = isEnabled | ||
} | ||
.disposed(by: disposeBag) | ||
|
||
output.isSucceed | ||
.drive(with: self) { owner, isSucceed in | ||
if isSucceed { | ||
owner.navigateToSetReadyCompleted() | ||
} | ||
} | ||
.disposed(by: disposeBag) | ||
Comment on lines
+130
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래와 같이 구현할 수도 있을 것 같네요. output.isSucceed
.filter { $0 } // 성공 조건 필터링
.drive(with: self) { owner, _ in
owner.navigateToSetReadyCompleted()
}
.disposed(by: disposeBag) |
||
} | ||
|
||
@objc | ||
private func doneButtonDidTap(_ sender: UIButton) { | ||
viewModel.updateReadyInfo() | ||
private func setupTextField(textField: UITextField) { | ||
let textFieldEvent = Observable.merge( | ||
textField.rx.controlEvent(.editingDidBegin).map { UIColor.maincolor.cgColor }, | ||
textField.rx.controlEvent(.editingDidEnd).map { UIColor.gray3.cgColor }, | ||
textField.rx.controlEvent(.editingDidEndOnExit).map { UIColor.gray3.cgColor } | ||
) | ||
Comment on lines
+140
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 와 진짜 깔끔하네요 ... 수야미 짱 |
||
|
||
textFieldEvent | ||
.bind { borderColor in | ||
textField.layer.borderColor = borderColor | ||
} | ||
.disposed(by: disposeBag) | ||
Comment on lines
+139
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3가지의 이벤트에 대해 위와 같이 구현하여, 반복되는 코드를 줄인 것이 대단한 것 같습니다. 👍 |
||
} | ||
|
||
private func navigateToSetReadyCompleted() { | ||
let setReadyCompletedViewController = SetReadyCompletedViewController() | ||
self.navigationController?.pushViewController( | ||
setReadyCompletedViewController, | ||
animated: true | ||
) | ||
} | ||
|
||
// MARK: - Keyboard Dismissal | ||
|
||
private func setupTapGesture() { | ||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) | ||
view.addGestureRecognizer(tapGesture) | ||
private func showToast(_ message: String, bottomInset: CGFloat = 128) { | ||
guard let view else { return } | ||
Comment on lines
+161
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
Toast().show(message: message, view: view, position: .bottom, inset: bottomInset) | ||
} | ||
|
||
@objc private func dismissKeyboard() { | ||
view.endEditing(true) | ||
private func setTextFieldDelegate() { | ||
let textFields: [(UITextField, String)] = [ | ||
(rootView.readyHourTextField, "readyHour"), | ||
(rootView.readyMinuteTextField, "readyMinute"), | ||
(rootView.moveHourTextField, "moveHour"), | ||
(rootView.moveMinuteTextField, "moveMinute") | ||
] | ||
|
||
textFields.forEach { (textField, identifier) in | ||
textField.delegate = self | ||
textField.keyboardType = .numberPad | ||
textField.accessibilityIdentifier = identifier | ||
} | ||
Comment on lines
+176
to
+178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래의 두 내용은 View 클래스에서 선언되어도 되지 않나 하는 생각이 있네요. |
||
} | ||
} | ||
|
||
|
||
// MARK: - UITextFieldDelegate | ||
|
||
extension SetReadyInfoViewController: UITextFieldDelegate { | ||
func textFieldDidBeginEditing(_ textField: UITextField) { | ||
textField.layer.borderColor = UIColor.maincolor.cgColor | ||
} | ||
|
||
func textFieldDidEndEditing(_ textField: UITextField) { | ||
textField.layer.borderColor = UIColor.gray3.cgColor | ||
|
||
if let text = textField.text, !text.isEmpty { | ||
viewModel.updateTime( | ||
textField: textField.accessibilityIdentifier ?? "", | ||
time: textField.text ?? "" | ||
) | ||
} | ||
} | ||
|
||
func textField( | ||
_ textField: UITextField, | ||
shouldChangeCharactersIn range: NSRange, | ||
|
@@ -143,91 +193,3 @@ extension SetReadyInfoViewController: UITextFieldDelegate { | |
return allowedCharacters.isSuperset(of: characterSet) | ||
} | ||
} | ||
|
||
|
||
// MARK: - Function | ||
|
||
private extension SetReadyInfoViewController { | ||
func setupTextField() { | ||
/// 저장된 준비 시간이 0이 아니면 텍스트 필드에 설정 | ||
if viewModel.storedReadyHour != 0 || viewModel.storedReadyMinute != 0 { | ||
rootView.readyHourTextField.text = String(viewModel.storedReadyHour) | ||
rootView.readyMinuteTextField.text = String(viewModel.storedReadyMinute) | ||
} | ||
|
||
/// 저장된 이동 시간이 0이 아니면 텍스트 필드에 설정 | ||
if viewModel.storedMoveHour != 0 || viewModel.storedMoveMinute != 0 { | ||
rootView.moveHourTextField.text = String(viewModel.storedMoveHour) | ||
rootView.moveMinuteTextField.text = String(viewModel.storedMoveMinute) | ||
} | ||
|
||
viewModel.checkValid( | ||
readyHourText: rootView.readyHourTextField.text ?? "", | ||
readyMinuteText: rootView.readyMinuteTextField.text ?? "", | ||
moveHourText: rootView.moveHourTextField.text ?? "", | ||
moveMinuteText: rootView.moveMinuteTextField.text ?? "" | ||
) | ||
} | ||
|
||
func setTextFieldDelegate() { | ||
let textFields: [(UITextField, String)] = [ | ||
(rootView.readyHourTextField, "readyHour"), | ||
(rootView.readyMinuteTextField, "readyMinute"), | ||
(rootView.moveHourTextField, "moveHour"), | ||
(rootView.moveMinuteTextField, "moveMinute") | ||
] | ||
|
||
textFields.forEach { (textField, identifier) in | ||
textField.delegate = self | ||
textField.keyboardType = .numberPad | ||
textField.accessibilityIdentifier = identifier | ||
} | ||
} | ||
|
||
func showToast(_ message: String, bottomInset: CGFloat = 128) { | ||
guard let view else { return } | ||
Toast().show(message: message, view: view, position: .bottom, inset: bottomInset) | ||
} | ||
|
||
// MARK: - Data Bind | ||
|
||
func setupBinding() { | ||
viewModel.readyHour.bind { [weak self] readyHour in | ||
self?.rootView.readyHourTextField.text = readyHour | ||
} | ||
|
||
viewModel.readyMinute.bind { [weak self] readyMinute in | ||
self?.rootView.readyMinuteTextField.text = readyMinute | ||
} | ||
|
||
viewModel.moveHour.bind { [weak self] moveHour in | ||
self?.rootView.moveHourTextField.text = moveHour | ||
} | ||
|
||
viewModel.moveMinute.bind { [weak self] moveMinute in | ||
self?.rootView.moveMinuteTextField.text = moveMinute | ||
} | ||
|
||
viewModel.isValid.bind { [weak self] isValid in | ||
self?.rootView.doneButton.isEnabled = isValid | ||
} | ||
|
||
viewModel.errMessage.bind { [weak self] err in | ||
if !err.isEmpty { | ||
self?.showToast(err) | ||
} | ||
} | ||
|
||
viewModel.isSucceedToSave.bind { [weak self] _ in | ||
if self?.viewModel.isSucceedToSave.value == true { | ||
DispatchQueue.main.async { | ||
let viewController = SetReadyCompletedViewController() | ||
self?.navigationController?.pushViewController( | ||
viewController, | ||
animated: true | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍