Skip to content

Commit

Permalink
refactor/#404 텍스트필드 조건 검사 및 입력 제한 로직 Rx 리팩토링
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaybei committed Dec 9, 2024
1 parent e73e46c commit cb7b5fd
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ final class SetReadyInfoViewController: BaseViewController {
setupBinding()
setupTapGesture()
setupTextField()
bindViewModel()
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -69,33 +70,48 @@ final class SetReadyInfoViewController: BaseViewController {
setupTextField(textField: rootView.moveHourTextField)
setupTextField(textField: rootView.moveMinuteTextField)

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
)
}

private func bindViewModel() {
let input = SetReadyInfoViewModel.Input(
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()
)

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)
}

private func setupTextField(textField: UITextField) {
let textFieldEvent = Observable.merge(
textField.rx.controlEvent(.editingDidBegin).map { UIColor.maincolor.cgColor },
Expand All @@ -110,18 +126,6 @@ final class SetReadyInfoViewController: BaseViewController {
.disposed(by: disposeBag)
}

@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 ?? ""
)
}

@objc
private func doneButtonDidTap(_ sender: UIButton) {
viewModel.updateReadyInfo()
Expand Down Expand Up @@ -204,31 +208,15 @@ private extension SetReadyInfoViewController {
// 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.errMessage.bind { [weak self] err in
// if !err.isEmpty {
// self?.showToast(err)
// }
// }

viewModel.isSucceedToSave.bind { [weak self] _ in
if self?.viewModel.isSucceedToSave.value == true {
Expand Down
116 changes: 87 additions & 29 deletions KkuMulKum/Source/SetReadyInfo/ViewModel/SetReadyInfoViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,28 @@
import Foundation
import UserNotifications

import RxCocoa
import RxSwift

enum Time {
case hour
case minute
}

final class SetReadyInfoViewModel {
let promiseID: Int
let promiseName: String
let promiseTime: String

let isValid = ObservablePattern<Bool>(false)
let errMessage = ObservablePattern<String>("")
let isSucceedToSave = ObservablePattern<Bool>(false)

var errMessage: String = ""

let readyHourRelay = BehaviorRelay<Int>(value: 0)
let readyMinuteRelay = BehaviorRelay<Int>(value: 0)
let moveHourRelay = BehaviorRelay<Int>(value: 0)
let moveMinuteRelay = BehaviorRelay<Int>(value: 0)

var readyHour = ObservablePattern<String>("")
var readyMinute = ObservablePattern<String>("")
Expand Down Expand Up @@ -53,15 +66,6 @@ final class SetReadyInfoViewModel {
self.notificationManager = notificationManager
}

private func validTime(time: Int, range: ClosedRange<Int>, defaultValue: String) -> String {
if range.contains(time) {
return String(time)
} else {
errMessage.value = "시간은 23시간 59분까지만 입력할 수 있어요!"
return defaultValue
}
}

private func calculateTimes() {
let readyHours = Int(readyHour.value) ?? storedReadyHour
let readyMinutes = Int(readyMinute.value) ?? storedReadyMinute
Expand All @@ -72,25 +76,6 @@ final class SetReadyInfoViewModel {
moveTime = moveHours * 60 + moveMinutes
}

func updateTime(textField: String, time: String) {
guard let time = Int(time) else { return }

switch textField {
case "readyHour":
readyHour.value = validTime(time: time, range: 0...23, defaultValue: "23")
case "readyMinute":
readyMinute.value = validTime(time: time, range: 0...59, defaultValue: "59")
case "moveHour":
moveHour.value = validTime(time: time, range: 0...23, defaultValue: "23")
case "moveMinute":
moveMinute.value = validTime(time: time, range: 0...59, defaultValue: "59")
default:
break
}

calculateTimes()
}

func checkValid(readyHourText: String,
readyMinuteText: String,
moveHourText: String,
Expand Down Expand Up @@ -209,3 +194,76 @@ final class SetReadyInfoViewModel {
}
}
}

extension SetReadyInfoViewModel: ViewModelType {
struct Input {
let readyHourText: Observable<String>
let readyMinuteText: Observable<String>
let moveHourText: Observable<String>
let moveMinuteText: Observable<String>
}

struct Output {
let readyHourText: Driver<String>
let readyMinuteText: Driver<String>
let moveHourText: Driver<String>
let moveMinuteText: Driver<String>
}

func transform(input: Input, disposeBag: RxSwift.DisposeBag) -> Output {
input.readyHourText
.distinctUntilChanged()
.compactMap { Int($0) }
.bind(to: readyHourRelay)
.disposed(by: disposeBag)

input.readyMinuteText
.distinctUntilChanged()
.compactMap { Int($0) }
.bind(to: readyMinuteRelay)
.disposed(by: disposeBag)

input.moveHourText
.distinctUntilChanged()
.compactMap { Int($0) }
.bind(to: moveHourRelay)
.disposed(by: disposeBag)

input.moveMinuteText
.distinctUntilChanged()
.compactMap { Int($0) }
.bind(to: moveMinuteRelay)
.disposed(by: disposeBag)

let readyHourText = checkValidTime(time: .hour, relay: readyHourRelay)
let readyMinuteText = checkValidTime(time: .minute, relay: readyMinuteRelay)
let moveHourText = checkValidTime(time: .hour, relay: moveHourRelay)
let moveMinuteText = checkValidTime(time: .minute, relay: moveMinuteRelay)

let output = Output(
readyHourText: readyHourText,
readyMinuteText: readyMinuteText,
moveHourText: moveHourText,
moveMinuteText: moveMinuteText
)

return output
}
}

private extension SetReadyInfoViewModel {
func checkValidTime(time: Time, relay: BehaviorRelay<Int>) -> Driver<String> {
let range: ClosedRange<Int> = time == .hour ? 0...23 : 0...59
return relay
.map { value in
if range.contains(value) {
return value.description
} else {
self.errMessage = "시간은 23시간 59분까지만 입력할 수 있어요!"
print(self.errMessage)
return String(range.upperBound)
}
}
.asDriver(onErrorJustReturn: "0")
}
}

0 comments on commit cb7b5fd

Please sign in to comment.