From 82804cfc3d1e2324f4b383a32d05f39e49b8ae69 Mon Sep 17 00:00:00 2001 From: Kim-Coding Date: Wed, 5 Jul 2023 14:40:58 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=9F=AC=EB=8B=9D=20=EB=AA=A9=ED=91=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presenter/View/Base/BaseView.swift | 3 + .../RecordRunningCoordinator.swift | 6 +- .../GoalSettingViewController.swift | 86 +++++++------------ .../RecordView/RecordViewController.swift | 36 ++++---- .../RunningStartViewController.swift | 72 +++++----------- .../UI/GoalSettingLabelStackView.swift | 2 +- .../UI/GoalSettingStackView.swift | 34 +++++--- .../MeasureRunning/UI/GoalTextField.swift | 22 +++-- .../RecordRunningViewController.swift | 2 +- .../ViewModel/RunningStartViewModel.swift | 62 ++++++++++--- 10 files changed, 159 insertions(+), 166 deletions(-) diff --git a/RunningCrew/Sources/Presenter/View/Base/BaseView.swift b/RunningCrew/Sources/Presenter/View/Base/BaseView.swift index c0f2256..a3c643a 100644 --- a/RunningCrew/Sources/Presenter/View/Base/BaseView.swift +++ b/RunningCrew/Sources/Presenter/View/Base/BaseView.swift @@ -6,9 +6,12 @@ // import UIKit +import RxSwift class BaseView: UIView { + var disposeBag = DisposeBag() + override init(frame: CGRect) { super.init(frame: frame) setupUI() diff --git a/RunningCrew/Sources/Presenter/View/RecordRunning/Coordinator/RecordRunningCoordinator.swift b/RunningCrew/Sources/Presenter/View/RecordRunning/Coordinator/RecordRunningCoordinator.swift index 5a80418..0c190ea 100644 --- a/RunningCrew/Sources/Presenter/View/RecordRunning/Coordinator/RecordRunningCoordinator.swift +++ b/RunningCrew/Sources/Presenter/View/RecordRunning/Coordinator/RecordRunningCoordinator.swift @@ -42,7 +42,7 @@ extension RecordRunningCoordinator { extension RecordRunningCoordinator: RecordRunningViewControllerDelegate { func showIndividualView() { - let runningStartVC: RunningStartViewController = RunningStartViewController(viewModel: RunningStartViewModel(viewTitle: "개인러닝")) + let runningStartVC: RunningStartViewController = RunningStartViewController(viewModel: RunningStartViewModel()) runningStartVC.delegate = self self.navigationController.pushViewController(runningStartVC, animated: true) } @@ -54,8 +54,8 @@ extension RecordRunningCoordinator: RecordRunningViewControllerDelegate { } extension RecordRunningCoordinator: RunningStartViewControllerDelegate { - func showGoalSettingView(goalType: GoalType, viewModel: RunningStartViewModel) { - let goalSettingVC = GoalSettingViewController(goalType: goalType, viewModel: viewModel) + func showGoalSettingView(viewModel: RunningStartViewModel) { + let goalSettingVC = GoalSettingViewController(viewModel: viewModel) goalSettingVC.delegate = self self.navigationController.pushViewController(goalSettingVC, animated: false) } diff --git a/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/GoalSettingView/GoalSettingViewController.swift b/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/GoalSettingView/GoalSettingViewController.swift index 74438b1..84aefa1 100644 --- a/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/GoalSettingView/GoalSettingViewController.swift +++ b/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/GoalSettingView/GoalSettingViewController.swift @@ -18,12 +18,10 @@ protocol GoalSettingViewDelegate: AnyObject { class GoalSettingViewController: BaseViewController { weak var delegate: GoalSettingViewDelegate? - - let goalType: GoalType + let viewModel: RunningStartViewModel - init(goalType: GoalType, viewModel: RunningStartViewModel) { - self.goalType = goalType + init(viewModel: RunningStartViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -39,13 +37,13 @@ class GoalSettingViewController: BaseViewController { } lazy var goalLabelBindingTextField: GoalTextField = { - let textField = GoalTextField(goalType: goalType) + let textField = GoalTextField(goalType: viewModel.goalType) return textField }() lazy var goalLabel: GoalSettingStackView = { - let goalLabel = GoalSettingStackView(goalType: goalType) + let goalLabel = GoalSettingStackView(goalType: viewModel.goalType) return goalLabel }() @@ -79,77 +77,53 @@ class GoalSettingViewController: BaseViewController { } override func bind() { + let input = RunningStartViewModel.Input( + nextButtonDidTap: goalLabel.nextButton.rx.tap.asObservable(), + beforeButtonDidTap: goalLabel.beforeButton.rx.tap.asObservable(), + navigationRightButtonDidTap: navigationItem.rightBarButtonItem?.rx.tap + .map { self.goalLabel.goalSettingLabelStackView.destinationLabel.text ?? "" }) + + let output = viewModel.transform(input: input) + + output.goalText + .drive(goalLabel.goalSettingLabelStackView.destinationLabel.rx.text) + .disposed(by: disposeBag) + goalLabelBindingTextField.rx.text.orEmpty .map { [weak self] input -> String? in guard let self = self else { return "" } - switch self.goalType { + switch self.viewModel.goalType.value { case .distance: if input.isEmpty { - return "0" + return "0.00" + } else if input.count <= 2 { + return input + ".00" + } else { + goalLabelBindingTextField.text = String(input.prefix(2)) + return (goalLabelBindingTextField.text ?? "0") + ".00" } - if input == "0" { - self.goalLabelBindingTextField.text?.removeLast() - return "0" - } - return input case .time: if input.isEmpty { return "00:00" + } else if input.count <= 4 { + let string = String(format: "%.4d", Int(input) ?? 0) + return String(string.prefix(2)) + ":" + String(string.suffix(2)) + } else { + goalLabelBindingTextField.text = String(input.prefix(4)) + return (goalLabelBindingTextField.text ?? "00").prefix(2) + ":" + (goalLabelBindingTextField.text ?? "00").suffix(2) } - let len = input.count - let paddedStr = String(repeating: "0", count: 4 - len) + input - let index1 = paddedStr.index(paddedStr.startIndex, offsetBy: 2) - let index2 = paddedStr.index(paddedStr.startIndex, offsetBy: 4) - let result = "\(paddedStr[.. + var disposeBag = DisposeBag() - init(goalType: GoalType) { + init(goalType: BehaviorRelay) { self.goalType = goalType super.init(frame: .zero) - setTitle() setCurrentLabelStackView() setDestinationStackView() setButtonConstraint() + bind() spacing = 11 } - func setTitle() { - switch goalType { - case .distance: - self.currentLabel.text = "킬로미터" - case .time: - self.currentLabel.text = "시간 : 분" - } - } - required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -145,4 +139,20 @@ class GoalSettingStackView: UIStackView { nextButton.bottomAnchor.constraint(equalTo: nextButtonBackgroundView.bottomAnchor) ]) } + + func bind() { + goalType.bind { type in + switch type { + case .distance: + self.currentLabel.text = "킬로미터" + self.beforeButton.isHidden = true + self.nextButton.isHidden = false + case .time: + self.currentLabel.text = "시간 : 분" + self.beforeButton.isHidden = false + self.nextButton.isHidden = true + } + } + .disposed(by: disposeBag) + } } diff --git a/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/UI/GoalTextField.swift b/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/UI/GoalTextField.swift index b146753..dd24f84 100644 --- a/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/UI/GoalTextField.swift +++ b/RunningCrew/Sources/Presenter/View/RecordRunning/MeasureRunning/UI/GoalTextField.swift @@ -7,13 +7,16 @@ import UIKit import RxCocoa +import RxRelay +import RxSwift class GoalTextField: UITextField { - var type: GoalType + var goalType: BehaviorRelay + var disposeBag = DisposeBag() - init(goalType: GoalType) { - self.type = goalType + init(goalType: BehaviorRelay) { + self.goalType = goalType super.init(frame: .zero) backgroundColor = .clear borderStyle = .none @@ -22,12 +25,15 @@ class GoalTextField: UITextField { } func setKeyboard() { - switch type { - case .distance: - self.keyboardType = .decimalPad - case .time: - self.keyboardType = .numberPad + goalType.bind { type in + switch type { + case .distance: + self.keyboardType = .decimalPad + case .time: + self.keyboardType = .numberPad + } } + .disposed(by: disposeBag) } required init?(coder: NSCoder) { diff --git a/RunningCrew/Sources/Presenter/View/RecordRunning/RecordRunningViewController.swift b/RunningCrew/Sources/Presenter/View/RecordRunning/RecordRunningViewController.swift index 9378514..46a03f9 100644 --- a/RunningCrew/Sources/Presenter/View/RecordRunning/RecordRunningViewController.swift +++ b/RunningCrew/Sources/Presenter/View/RecordRunning/RecordRunningViewController.swift @@ -168,7 +168,7 @@ final class RecordRunningViewController: BaseViewController { output.isNeedLocationAuth .bind { [weak self] isNeed in if isNeed { - //TODO: 위치 권한 요청 팝업 + //TODO: 위치 권한 요청 Alert } } .disposed(by: disposeBag) diff --git a/RunningCrew/Sources/Presenter/ViewModel/RunningStartViewModel.swift b/RunningCrew/Sources/Presenter/ViewModel/RunningStartViewModel.swift index 3197a04..d973336 100644 --- a/RunningCrew/Sources/Presenter/ViewModel/RunningStartViewModel.swift +++ b/RunningCrew/Sources/Presenter/ViewModel/RunningStartViewModel.swift @@ -8,27 +8,61 @@ import Foundation import RxCocoa import RxSwift +import RxRelay -class RunningStartViewModel { - - let viewTitle: String +final class RunningStartViewModel: BaseViewModelType { + var goalType: BehaviorRelay = BehaviorRelay(value: .distance) var goalDistance: BehaviorRelay = BehaviorRelay(value: 5.00) var goalHour: BehaviorRelay = BehaviorRelay(value: 0) var goalMinute: BehaviorRelay = BehaviorRelay(value: 0) - var goalTimeRelay = BehaviorRelay(value: "") - let disposeBag = DisposeBag() + var disposeBag = DisposeBag() - init(viewTitle: String) { - self.viewTitle = viewTitle - Observable.combineLatest(goalHour, goalMinute).map({ hour, minute in - "\(String(format: "%.2d", hour)):\(String(format: "%.2d", minute))" - }).bind(to: goalTimeRelay) - .disposed(by: disposeBag) + struct Input { + let nextButtonDidTap: Observable + let beforeButtonDidTap: Observable + let navigationRightButtonDidTap: Observable? } + struct Output { + let goalText: Driver + } + + func transform(input: Input) -> Output { + input.nextButtonDidTap + .bind { self.goalType.accept(.time) } + .disposed(by: disposeBag) + + input.beforeButtonDidTap + .bind { self.goalType.accept(.distance) } + .disposed(by: disposeBag) + + input.navigationRightButtonDidTap? + .bind { [weak self] text in + guard let self = self else { return } + + switch goalType.value { + case .distance: + goalDistance.accept(Float(text) ?? 0) + case .time: + let time = (text).split(separator: ":").map {Int($0)} + + goalHour.accept(time[0] ?? 0 ) + goalMinute.accept(time[1] ?? 0) + } + } + .disposed(by: disposeBag) + + let goalText = Observable.combineLatest(goalType, goalDistance, goalHour, goalMinute) + .map { type, distance, hour, minute in + switch type { + case .distance: return String(format: "%.2f", distance) + case .time: return String(format: "%.2d", hour) + ":" + String(format: "%.2d", minute) + } + } + .asDriver(onErrorJustReturn: "") + + return Output(goalText: goalText) + } } - -// Relay 스스로가 관찰 가능하고 관찰을 받을 수 있는 것 -// ViewModel의 목적이 비지니스 로직의 분리