From 1998bc401a1a99318c09507d39250d169b680b77 Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Thu, 19 Mar 2026 12:21:40 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20=EC=B2=98=EC=9D=8C=20=EC=B6=9C?= =?UTF-8?q?=EB=B0=9C=EC=A7=80=20=EA=B8=B0=ED=83=80=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=8B=9C=20=ED=82=A4=ED=8C=A8=EB=93=9C=EC=99=80=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=ED=95=84=EB=93=9C=EA=B0=80=20=EA=B2=B9?= =?UTF-8?q?=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/BottomSheetViewControllerB.swift | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/Koin/Core/View/BottomSheetViewControllerB.swift b/Koin/Core/View/BottomSheetViewControllerB.swift index fe91e6fb..524038ef 100644 --- a/Koin/Core/View/BottomSheetViewControllerB.swift +++ b/Koin/Core/View/BottomSheetViewControllerB.swift @@ -17,6 +17,7 @@ protocol BottomSheetViewControllerBDelegate: AnyObject { final class BottomSheetViewControllerB: UIViewController { // MARK: - Properties + private var contentViewBottomConstraint: Constraint? private var safeAreaHeightConstraint: Constraint? private var alpha: CGFloat @@ -54,7 +55,6 @@ final class BottomSheetViewControllerB: UIViewController { override func viewDidLoad() { super.viewDidLoad() configureView() - addObserver() setGesture() hideKeyboardWhenTappedAround() } @@ -69,33 +69,26 @@ final class BottomSheetViewControllerB: UIViewController { extension BottomSheetViewControllerB: BottomSheetViewControllerBDelegate { func present() { - contentView.snp.remakeConstraints { - $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) - $0.leading.trailing.equalToSuperview() - } +// view.layoutIfNeeded() + contentViewBottomConstraint?.update(offset: 0) UIView.animate(withDuration: 0.25) { [weak self] in guard let self else { return } dimView.alpha = alpha - view.setNeedsLayout() view.layoutIfNeeded() } } func dismiss() { - contentView.snp.remakeConstraints { - $0.top.equalTo(view.snp.bottom) - $0.leading.trailing.equalToSuperview() - } + contentViewBottomConstraint?.update(offset: contentView.bounds.height) UIView.animate( withDuration: 0.25, animations: { [weak self] in guard let self else { return } dimView.alpha = 0 - view.setNeedsLayout() view.layoutIfNeeded() }, completion: { [weak self] _ in - self?.dismiss(animated: true) + self?.dismiss(animated: false) }) } } @@ -107,34 +100,10 @@ extension BottomSheetViewControllerB { dimView.addGestureRecognizer(tapGesture) } - private func addObserver() { - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) - } - @objc private func dimViewTapped() { dismissKeyboard() dismiss() } - - @objc private func keyboardWillShow(_ notification: Notification) { - contentView.snp.remakeConstraints { - $0.bottom.equalTo(view.keyboardLayoutGuide.snp.top) - $0.leading.trailing.equalToSuperview() - } - UIView.animate(withDuration: 0.25) { [weak self] in - self?.view.layoutIfNeeded() - } - } - @objc private func keyboardWillHide(_ notification: Notification) { - contentView.snp.remakeConstraints { - $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) - $0.leading.trailing.equalToSuperview() - } - UIView.animate(withDuration: 0.25) { [weak self] in - self?.view.layoutIfNeeded() - } - } } extension BottomSheetViewControllerB { @@ -155,7 +124,8 @@ extension BottomSheetViewControllerB { $0.edges.equalToSuperview() } contentView.snp.makeConstraints { - $0.top.equalTo(view.snp.bottom) + contentView.layoutIfNeeded() + contentViewBottomConstraint = $0.bottom.equalTo(view.keyboardLayoutGuide.snp.top).offset(contentView.bounds.height).constraint $0.leading.trailing.equalToSuperview() } safeAreaView.snp.makeConstraints { From f2c8841048b5766fa0c67f95cd59250fc97be11a Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Thu, 19 Mar 2026 12:23:03 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=EC=B6=9C=EB=B0=9C=EC=A7=80=20?= =?UTF-8?q?=EA=B8=B0=ED=83=80=EB=A1=9C=20=EC=9E=91=EC=84=B1=20=ED=9B=84=20?= =?UTF-8?q?=EB=8F=84=EC=B0=A9=EC=A7=80=20=EC=9E=91=EC=84=B1=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=95=EB=AC=B8=EC=9C=BC=EB=A1=9C=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=EB=90=98=EC=96=B4=EC=9E=88=EC=A7=80=EB=A7=8C=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=ED=95=84=EB=93=9C=EC=97=90=EB=8A=94=20?= =?UTF-8?q?=EC=B6=9C=EB=B0=9C=EC=A7=80=EA=B0=80=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=EB=90=98=EC=96=B4=EC=9E=88=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CallVanPostPlaceBottomSheetView.swift | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostPlaceBottomSheetView.swift b/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostPlaceBottomSheetView.swift index 8cc46867..4ca69235 100644 --- a/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostPlaceBottomSheetView.swift +++ b/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostPlaceBottomSheetView.swift @@ -65,33 +65,30 @@ final class CallVanPostPlaceBottomSheetView: UIView { onApplyButtonTapped: @escaping (CallVanPlace, String?)->Void ) { self.onApplyButtonTapped = onApplyButtonTapped - + titleLabel.text = title.rawValue - + + resetState() + if place == .custom { (buttons1 + buttons2).forEach { $0.isSelected = false } customButton.isSelected = true customPlaceTextField.text = customPlace - customPlaceTextField.snp.remakeConstraints { - $0.height.equalTo(47) - $0.top.equalTo(separatorView.snp.bottom).offset(24) - $0.leading.trailing.equalToSuperview().inset(32) - } - applyButton.setAttributedTitle(NSAttributedString( - string: "입력완료", - attributes: [ - .font : UIFont.appFont(.pretendardBold, size: 16), - .foregroundColor : UIColor.appColor(.neutral0) - ]), for: .normal - ) + updateCustomPlaceTextField(isVisible: true) + updateApplyButtonTitle(isCustomSelected: true) + valiate(customPlaceTextField) } else { let place = place ?? CallVanPlace.frontGate (buttons1 + buttons2).forEach { - $0.isSelected = $0.filterState as! CallVanPlace == place + $0.isSelected = $0.filterState as? CallVanPlace == place } customButton.isSelected = false + updateCustomPlaceTextField(isVisible: false) + updateApplyButtonTitle(isCustomSelected: false) + applyButton.backgroundColor = UIColor.appColor(.new500) + applyButton.isEnabled = true } } required init?(coder: NSCoder) { @@ -126,12 +123,8 @@ extension CallVanPostPlaceBottomSheetView { } } customButton.isSelected = false - - customPlaceTextField.snp.remakeConstraints { - $0.height.equalTo(0) - $0.top.equalTo(separatorView.snp.bottom).offset(0) - $0.leading.trailing.equalToSuperview().inset(32) - } + + updateCustomPlaceTextField(isVisible: false) UIView.animate( withDuration: 0.2, animations: { [weak self] in @@ -139,14 +132,7 @@ extension CallVanPostPlaceBottomSheetView { self?.layoutIfNeeded() self?.applyButton.isEnabled = true self?.applyButton.backgroundColor = UIColor.appColor(.new500) - self?.applyButton.do { - $0.setAttributedTitle(NSAttributedString( - string: "선택하기", - attributes: [ - .font : UIFont.appFont(.pretendardBold, size: 16), - .foregroundColor : UIColor.appColor(.neutral0) - ]), for: .normal) - } + self?.updateApplyButtonTitle(isCustomSelected: false) }, completion: { [weak self] _ in self?.customPlaceTextField.resignFirstResponder() @@ -159,26 +145,14 @@ extension CallVanPostPlaceBottomSheetView { $0.isSelected = false } sender.isSelected = true - - customPlaceTextField.snp.remakeConstraints { - $0.height.equalTo(47) - $0.top.equalTo(separatorView.snp.bottom).offset(24) - $0.leading.trailing.equalToSuperview().inset(32) - } + + updateCustomPlaceTextField(isVisible: true) UIView.animate( withDuration: 0.2, animations: { [weak self] in self?.superview?.layoutIfNeeded() self?.layoutIfNeeded() - self?.applyButton.do { - $0.setAttributedTitle(NSAttributedString( - string: "입력완료", - attributes: [ - .font : UIFont.appFont(.pretendardBold, size: 16), - .foregroundColor : UIColor.appColor(.neutral0) - ]), for: .normal) - } - + self?.updateApplyButtonTitle(isCustomSelected: true) }, completion: { [weak self] _ in self?.customPlaceTextField.becomeFirstResponder() @@ -190,16 +164,14 @@ extension CallVanPostPlaceBottomSheetView { if let selectedButton = (buttons1 + buttons2).first(where: { $0.isSelected }), let selectedPlace = selectedButton.filterState as? CallVanPlace { onApplyButtonTapped?(selectedPlace, nil) - customPlaceTextField.resignFirstResponder() - delegate?.dismiss() } else if customButton.isSelected, let customPlace = customPlaceTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines), !customPlace.isEmpty { onApplyButtonTapped?(.custom, customPlace) - customPlaceTextField.resignFirstResponder() - delegate?.dismiss() } + customPlaceTextField.resignFirstResponder() + delegate?.dismiss() } } @@ -231,6 +203,38 @@ extension CallVanPostPlaceBottomSheetView: UITextFieldDelegate { } extension CallVanPostPlaceBottomSheetView { + + private func resetState() { + (buttons1 + buttons2).forEach { + $0.isSelected = false + } + customButton.isSelected = false + customPlaceTextField.text = nil + customPlaceTextField.resignFirstResponder() + updateCustomPlaceTextField(isVisible: false) + updateApplyButtonTitle(isCustomSelected: false) + applyButton.backgroundColor = UIColor.appColor(.new500) + applyButton.isEnabled = true + } + + private func updateCustomPlaceTextField(isVisible: Bool) { + customPlaceTextField.snp.remakeConstraints { + $0.height.equalTo(isVisible ? 47 : 0) + $0.top.equalTo(separatorView.snp.bottom).offset(isVisible ? 24 : 0) + $0.leading.trailing.equalToSuperview().inset(32) + } + } + + private func updateApplyButtonTitle(isCustomSelected: Bool) { + let title = isCustomSelected ? "입력완료" : "선택하기" + applyButton.setAttributedTitle(NSAttributedString( + string: title, + attributes: [ + .font : UIFont.appFont(.pretendardBold, size: 16), + .foregroundColor : UIColor.appColor(.neutral0) + ]), for: .normal + ) + } private func configureView() { setUpStyles() From 50e1cd6e836461d3c4e399a0765dbc446f7d72f5 Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Thu, 19 Mar 2026 12:27:27 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=EC=BD=9C=EB=B2=A4=ED=8C=9F=20?= =?UTF-8?q?=EB=AA=A8=EC=A7=91=20=EB=B2=84=ED=8A=BC=20=EB=88=84=EB=A5=B4?= =?UTF-8?q?=EB=A9=B4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=EC=9D=B4=20=EB=90=98?= =?UTF-8?q?=EC=96=B4=EC=9E=88=EC=96=B4=EB=8F=84=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=8C=9D=EC=97=85=EC=9D=B4=20=EB=9C=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Koin/Presentation/Home/Home/HomeViewController.swift | 1 + Koin/Presentation/Home/Home/HomeViewModel.swift | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Koin/Presentation/Home/Home/HomeViewController.swift b/Koin/Presentation/Home/Home/HomeViewController.swift index 5d428213..eaaef88d 100644 --- a/Koin/Presentation/Home/Home/HomeViewController.swift +++ b/Koin/Presentation/Home/Home/HomeViewController.swift @@ -176,6 +176,7 @@ final class HomeViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + inputSubject.send(.checkLogin) inputSubject.send(.getUserScreenAction(Date(), .enterVC)) inputSubject.send(.getUserScreenAction(Date(), .beginEvent, .mainShopCategories)) inputSubject.send(.categorySelected(getDiningPlace())) diff --git a/Koin/Presentation/Home/Home/HomeViewModel.swift b/Koin/Presentation/Home/Home/HomeViewModel.swift index 6ef88d5e..bef4556d 100644 --- a/Koin/Presentation/Home/Home/HomeViewModel.swift +++ b/Koin/Presentation/Home/Home/HomeViewModel.swift @@ -22,6 +22,7 @@ final class HomeViewModel: ViewModelProtocol { case getUserScreenAction(Date, ScreenActionType, EventParameter.EventLabelNeededDuration? = nil) case getNoticeBanner(Date?) case fetchBanner + case checkLogin case logSessionEvent(EventLabelType, EventParameter.EventCategory, Any, String) } @@ -85,7 +86,6 @@ final class HomeViewModel: ViewModelProtocol { input.sink { [weak self] input in switch input { case .viewDidLoad: - self?.checkLogin() self?.getShopCategory() self?.checkVersion() self?.fetchUserData() @@ -103,6 +103,8 @@ final class HomeViewModel: ViewModelProtocol { self?.getNoticeBanners(date: date) case .fetchBanner: self?.fetchBanner() + case .checkLogin: + self?.checkLogin() case let .logEventDirect(name, label, value, category): self?.logAnalyticsEventUseCase.logEvent(name: name, label: label, value: value, category: category) case let .logSessionEvent(label, category, value, sessionId): From 44f74b2fd6470647e7019bc5cc5f1d6f4c948b30 Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Thu, 19 Mar 2026 12:38:07 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20=EC=8B=9C=EA=B0=84=20=ED=91=9C?= =?UTF-8?q?=EA=B8=B0=20=EB=B0=A9=EC=8B=9D=20=ED=86=B5=EC=9D=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Koin/Data/DTOs/Decodable/CallVan/CallVanDataDto.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Koin/Data/DTOs/Decodable/CallVan/CallVanDataDto.swift b/Koin/Data/DTOs/Decodable/CallVan/CallVanDataDto.swift index 78f26fda..b798bf41 100644 --- a/Koin/Data/DTOs/Decodable/CallVan/CallVanDataDto.swift +++ b/Koin/Data/DTOs/Decodable/CallVan/CallVanDataDto.swift @@ -95,7 +95,7 @@ extension CallVanDataDto { formatter.dateFormat = "yyyy-MM-dd" formatter.locale = Locale(identifier: "ko_KR") if let date = formatter.date(from: departureDate) { - formatter.dateFormat = "MM.dd (a)" + formatter.dateFormat = "MM.dd (E)" return formatter.string(from: date) } else { return departureDate From 8ccb8f64565426713ba5ebbdfe7456dd224021f2 Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Sat, 21 Mar 2026 20:07:32 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EA=B3=BC=EA=B1=B0=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=9C=BC=EB=A1=9C=20=EC=B6=9C=EB=B0=9C=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=84=A4=EC=A0=95=20=EA=B0=80=EB=8A=A5,=20?= =?UTF-8?q?=EC=B6=9C=EB=B0=9C=EC=A7=80=EC=99=80=20=EB=8F=84=EC=B0=A9?= =?UTF-8?q?=EC=A7=80=EA=B0=80=20=EA=B0=99=EC=95=84=EB=8F=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KoinPickerDropDownViewDateDelegate.swift | 106 +++++++++++------- .../CallVanPost/CallVanPostViewModel.swift | 43 +++++-- .../Subviews/CallVanPostDateView.swift | 2 +- 3 files changed, 100 insertions(+), 51 deletions(-) diff --git a/Koin/Core/View/KoinPickerDropDownView/Delegates/KoinPickerDropDownViewDateDelegate.swift b/Koin/Core/View/KoinPickerDropDownView/Delegates/KoinPickerDropDownViewDateDelegate.swift index ff2ea74e..19998a99 100644 --- a/Koin/Core/View/KoinPickerDropDownView/Delegates/KoinPickerDropDownViewDateDelegate.swift +++ b/Koin/Core/View/KoinPickerDropDownView/Delegates/KoinPickerDropDownViewDateDelegate.swift @@ -11,14 +11,12 @@ import Then final class KoinPickerDropDownViewDateDelegate { // MARK: - Properties - let columnWidths: [CGFloat] = [53, 31, 31] + private var dates: [Int: [Int: [Int]]] = [:] + private let columnWidths: [CGFloat] = [53, 31, 31] - private let inputFormatter = DateFormatter().then { - $0.dateFormat = "yyyy-MM-dd" - } - - private let outputFormatter = DateFormatter().then { - $0.dateFormat = "yyyy년*M월*d일" + // MARK: - Initialzier + init(range: Range) { + resetDates(range: range) } } @@ -29,68 +27,90 @@ extension KoinPickerDropDownViewDateDelegate: KoinPickerDropDownViewDelegate { } func reset(koinPicker: KoinPickerDropDownView, initialDate: Date) { + let year = initialDate.year + let month = initialDate.month + let day = initialDate.day - let selectedItem = outputFormatter.string(from: initialDate).components(separatedBy: "*") - let items = getItems(selectedItem: selectedItem) + let items = getItems(year: year, month: month) + let selectedItem: [String] = ["\(year)년", "\(month)월", "\(day)일"] - koinPicker.reset(items: items, selectedItem: selectedItem, columnWidths: [53, 31, 31]) + koinPicker.reset(items: items, selectedItem: selectedItem, columnWidths: columnWidths) } func selectedItemUpdated(koinPicker: KoinPickerDropDownView, selectedItem: [String]) { - guard let yearInt = Int(selectedItem[0].filter { $0.isNumber }), - let monthInt = Int(selectedItem[1].filter { $0.isNumber }), - var dayInt = Int(selectedItem[2].filter { $0.isNumber }) else { + guard let selectedYear = Int(selectedItem[0].filter { $0.isNumber }), + var selectedMonth = Int(selectedItem[1].filter { $0.isNumber }), + var selectedDay = Int(selectedItem[2].filter { $0.isNumber }) else { assert(false) return } - let maxDay = getAllDays(year: yearInt, month: monthInt).count - if dayInt > maxDay { - dayInt = maxDay + let items = getItems(year: selectedYear, month: selectedMonth) + guard let minMonthString = items[1].first?.filter({ $0.isNumber}), + let minMonth = Int(minMonthString), + let maxMonthString = items[1].last?.filter({ $0.isNumber}), + let maxMonth = Int(maxMonthString) else { + assert(false) + return } - guard let validDate = inputFormatter.date(from: String(format: "%d-%d-%d", yearInt, monthInt, dayInt)) else { + guard let minDayString = items[2].first?.filter({ $0.isNumber }), + let minDay = Int(minDayString), + let maxDayString = items[2].last?.filter({ $0.isNumber }), + let maxDay = Int(maxDayString) else { assert(false) return } - reset(koinPicker: koinPicker, initialDate: validDate) + selectedMonth = min(max(selectedMonth, minMonth), maxMonth) + selectedDay = min(max(selectedDay, minDay), maxDay) + let selectedItem: [String] = ["\(selectedYear)년", "\(selectedMonth)월", "\(selectedDay)일"] + koinPicker.reset(items: items, selectedItem: selectedItem, columnWidths: columnWidths) } } extension KoinPickerDropDownViewDateDelegate { - private func getItems(selectedItem: [String]) -> [[String]] { - - guard let selectedYearInt = Int(selectedItem[0].filter { $0.isNumber }), - let selectedMonthInt = Int(selectedItem[1].filter { $0.isNumber }) else { - assert(false) - return [[],[],[]] + private func resetDates(range: Range) { + let calendar = Calendar.current + let startDay = calendar.startOfDay(for: Date()) + let availableDates = range.compactMap { + calendar.date(byAdding: .day, value: $0, to: startDay) } - let years: [String] = { - let thisYearInt = Date().year - let lowestYearInt = min(selectedYearInt, thisYearInt) - let heighestYearInt = thisYearInt + 1 - return Range(lowestYearInt...heighestYearInt).map { String($0)+"년" } - }() - let months: [String] = Range(1...12).map { String($0)+"월" } - let days: [String] = getAllDays(year: selectedYearInt, month: selectedMonthInt) + var dates: [Int: [Int: [Int]]] = [:] - return [years, months, days] + for date in availableDates { + if dates[date.year] == nil { + dates[date.year] = [:] + } + + if dates[date.year]?[date.month] == nil { + dates[date.year]?[date.month] = [] + } + + dates[date.year]?[date.month]?.append(date.day) + } + + self.dates = dates } - func getAllDays(year: Int, month: Int) -> [String] { - - let calendar = Calendar.current - var components = DateComponents() - components.year = year - components.month = month + private func getItems(year: Int, month: Int) -> [[String]] { + guard let minYear: Int = dates.keys.sorted().first, + let maxYear: Int = dates.keys.sorted().last else { + assert(false) + return [[],[],[]] + } + let year = min(max(minYear, year), maxYear) - guard let date = calendar.date(from: components), - let dayRange = calendar.range(of: .day, in: .month, for: date) else { + guard let minMonth: Int = dates[year]?.keys.sorted().first, + let maxMonth: Int = dates[year]?.keys.sorted().last else { assert(false) - return [] + return [[],[],[]] } + let month = min(max(minMonth, month), maxMonth) - return dayRange.map { String($0) + "일" } + let years: [String] = dates.keys.sorted().map { "\($0)년" } + let months: [String] = dates[year]!.keys.sorted().map { "\($0)월" } + let days: [String] = dates[year]![month]!.sorted().compactMap { "\($0)일" } + return [years, months, days] } } diff --git a/Koin/Presentation/CallVan/CallVanPost/CallVanPostViewModel.swift b/Koin/Presentation/CallVan/CallVanPost/CallVanPostViewModel.swift index 52b450d9..ab890b2f 100644 --- a/Koin/Presentation/CallVan/CallVanPost/CallVanPostViewModel.swift +++ b/Koin/Presentation/CallVan/CallVanPost/CallVanPostViewModel.swift @@ -37,7 +37,7 @@ final class CallVanPostViewModel: ViewModelProtocol { } private(set) var request = CallVanPostRequest() { didSet { - validateRequest() + validate() } } @@ -98,13 +98,42 @@ extension CallVanPostViewModel { outputSubject.send(.updateArrival(request.arrivalType, request.arrivalCustomName)) } - private func validateRequest() { - let validation = request.departureType != nil - && request.arrivalType != nil - && request.departureDate != nil - && request.departureTime != nil + private func validate() { + var isValid = true - outputSubject.send(.enablePostButton(validation)) + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm" + let tenMinutes: TimeInterval = 1 * 60 * 10 + + /// 빈 값이 있는지 확인 + if request.departureType == nil + || request.arrivalType == nil + || request.departureDate?.isEmpty == true + || request.departureTime?.isEmpty == true { + isValid = false + } + + /// 출발지와 도착지가 다른지 확인 + switch (request.departureType, request.arrivalType) { + case (.custom, .custom): + if request.departureCustomName == request.arrivalCustomName { + isValid = false + } + default: + if request.departureType == request.arrivalType { + isValid = false + } + } + + /// 현재 시각으로부터 최소 10분 이후 일정인지 확인 + let date = request.departureDate ?? "" + let time = request.departureTime ?? "" + if let requestDate = formatter.date(from: "\(date) \(time)"), + requestDate.timeIntervalSince(Date()) < tenMinutes { + isValid = false + } + + outputSubject.send(.enablePostButton(isValid)) } private func postData() { diff --git a/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostDateView.swift b/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostDateView.swift index 2b705e65..dae08cbf 100644 --- a/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostDateView.swift +++ b/Koin/Presentation/CallVan/CallVanPost/Subviews/CallVanPostDateView.swift @@ -25,7 +25,7 @@ final class CallVanPostDateView: ExtendedTouchAreaView { private let dateButton = UIButton() private let dateLabel = UILabel() private let downArrowImageView = UIImageView() - private let dateDropDownView = KoinPickerDropDownView(delegate: KoinPickerDropDownViewDateDelegate()) + private let dateDropDownView = KoinPickerDropDownView(delegate: KoinPickerDropDownViewDateDelegate(range: 0..<365)) // MARK: - Initializer init() { From c424bb7f84d9aea1c6701b7564171a8309163c81 Mon Sep 17 00:00:00 2001 From: GI JEONG HONG Date: Sat, 21 Mar 2026 20:24:34 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=8B=A4=ED=8C=A8=EC=8B=9C=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CallVan/CallVanChat/CallVanChatViewModel.swift | 6 +++++- .../CallVanData/CallVanDataViewController.swift | 2 ++ .../CallVan/CallVanData/CallVanDataViewModel.swift | 13 +++++++++++-- .../CallVan/CallVanList/CallVanListViewModel.swift | 6 +++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewModel.swift b/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewModel.swift index fbd8f353..930cba7a 100644 --- a/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewModel.swift +++ b/Koin/Presentation/CallVan/CallVanChat/CallVanChatViewModel.swift @@ -125,7 +125,11 @@ extension CallVanChatViewModel { private func fetchData() { fetchCallVanDataUseCase.execute(postId: postId).sink( - receiveCompletion: { _ in }, + receiveCompletion: { [weak self] comepltion in + if case .failure(let error) = comepltion { + self?.outputSubject.send(.showToast(error.message)) + } + }, receiveValue: { [weak self] callVanData in self?.outputSubject.send(.updateData(callVanData)) } diff --git a/Koin/Presentation/CallVan/CallVanData/CallVanDataViewController.swift b/Koin/Presentation/CallVan/CallVanData/CallVanDataViewController.swift index 9180f9af..eefc9a96 100644 --- a/Koin/Presentation/CallVan/CallVanData/CallVanDataViewController.swift +++ b/Koin/Presentation/CallVan/CallVanData/CallVanDataViewController.swift @@ -68,6 +68,8 @@ extension CallVanDataViewController { participantsTableView.configure(participants: callVanData.participants) case let .updateBell(alert): configureRightBarButton(alert: alert) + case let .showToast(message): + showToastMessage(message: message) } refreshControl.endRefreshing() }.store(in: &subscriptions) diff --git a/Koin/Presentation/CallVan/CallVanData/CallVanDataViewModel.swift b/Koin/Presentation/CallVan/CallVanData/CallVanDataViewModel.swift index 20694af9..0d74c419 100644 --- a/Koin/Presentation/CallVan/CallVanData/CallVanDataViewModel.swift +++ b/Koin/Presentation/CallVan/CallVanData/CallVanDataViewModel.swift @@ -13,6 +13,7 @@ final class CallVanDataViewModel: ViewModelProtocol { enum Output { case update(CallVanData) case updateBell(alert: Bool) + case showToast(String) } enum Input { case viewWillAppear @@ -64,7 +65,11 @@ extension CallVanDataViewModel { private func fetchData() { fetchCallVanDataUseCase.execute(postId: postId).sink( - receiveCompletion: { _ in }, + receiveCompletion: { [weak self] completion in + if case .failure(let error) = completion { + self?.outputSubject.send(.showToast(error.message)) + } + }, receiveValue: { [weak self] callVanData in self?.outputSubject.send(.update(callVanData)) } @@ -73,7 +78,11 @@ extension CallVanDataViewModel { private func fetchNotification() { fetchCallVanNotificationListUseCase.execute().sink( - receiveCompletion: { _ in }, + receiveCompletion: { [weak self] completion in + if case .failure(let error) = completion { + self?.outputSubject.send(.showToast(error.message)) + } + }, receiveValue: { [weak self] notifications in let alert = notifications.filter { !$0.isRead }.count != 0 self?.outputSubject.send(.updateBell(alert: alert)) diff --git a/Koin/Presentation/CallVan/CallVanList/CallVanListViewModel.swift b/Koin/Presentation/CallVan/CallVanList/CallVanListViewModel.swift index b5af2e38..69bbda38 100644 --- a/Koin/Presentation/CallVan/CallVanList/CallVanListViewModel.swift +++ b/Koin/Presentation/CallVan/CallVanList/CallVanListViewModel.swift @@ -129,7 +129,11 @@ extension CallVanListViewModel { private func fetchNotification() { fetchCallVanNotificationListUseCase.execute().sink( - receiveCompletion: { _ in }, + receiveCompletion: { [weak self] completion in + if case .failure(let error) = completion { + self?.outputSubject.send(.showToast(error.message)) + } + }, receiveValue: { [weak self] notifications in let alert = notifications.filter { !$0.isRead }.count != 0 self?.outputSubject.send(.updateBell(alert: alert))