From bc22fb25104517f21ce43a10ee15b7c90cfaecdb Mon Sep 17 00:00:00 2001 From: JunYoung Date: Thu, 19 Feb 2026 01:57:51 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20level=20range=20nil=EA=B0=92=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ItemFilterBottomSheetReactor.swift | 6 ++-- .../ItemFilterBottomSheetViewController.swift | 12 ++++---- .../Views/FilterLevelSectionView.swift | 16 +++++------ .../Views/FilterSlider.swift | 28 +++++++++---------- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift index 4a2e3099..32fa0fa1 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift @@ -16,7 +16,7 @@ public final class ItemFilterBottomSheetReactor: Reactor { case closeButtonTapped case filterSelected(indexPath: IndexPath) case filterDeselected(indexPath: IndexPath) - case changeLevelRange(low: Int, high: Int) + case changeLevelRange(low: Int?, high: Int?) case applyButtonTapped([(String, String)]) case resetFilters } @@ -26,7 +26,7 @@ public final class ItemFilterBottomSheetReactor: Reactor { case setScrolls(selectedIndex: Int?) case appendSelectedItem(indexPath: IndexPath) case removeSelectedItem(indexPath: IndexPath) - case setLevelRange(low: Int, high: Int) + case setLevelRange(low: Int?, high: Int?) case resetFilters } @@ -47,7 +47,7 @@ public final class ItemFilterBottomSheetReactor: Reactor { var etcItems: [String] = ["마스터리북", "스킬북", "소비", "설치", "이동수단"] var selectedScrollIndexes: Int? var selectedItemIndexes: [IndexPath] = [] - var levelRange: (low: Int, high: Int) = (1, 200) + var levelRange: (low: Int?, high: Int?) = (nil, nil) @Pulse var route: Route = .none } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift index 20c8298b..110dd2eb 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift @@ -221,10 +221,12 @@ private extension ItemFilterBottomSheetViewController { case .level: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FilterLevelSectionCell.identifier, for: indexPath) as! FilterLevelSectionCell guard let reactor = self.reactor else { return UICollectionViewCell() } - let rowValue = cell.levelSectionView.slider.lowerValueObservable.map { Int($0) } - let highValue = cell.levelSectionView.slider.upperValueObservable.map { Int($0) } - Observable.combineLatest(rowValue, highValue) - .map { low, high in Reactor.Action.changeLevelRange(low: low, high: high) } + let lowValue = cell.levelSectionView.slider.lowerValueObservable + let highValue = cell.levelSectionView.slider.upperValueObservable + Observable.combineLatest(lowValue, highValue) + .map { low, high in + return Reactor.Action.changeLevelRange(low: low.map { Int($0)}, high: high.map { Int($0)}) + } .bind(to: reactor.action) .disposed(by: cell.disposeBag) @@ -571,7 +573,7 @@ extension ItemFilterBottomSheetViewController: UICollectionViewDataSource { return reactor.currentState.etcItems[indexPath.row] case .level: let range = reactor.currentState.levelRange - return "\(range.low) ~ \(range.high)" + return "\(range.low ?? 0) ~ \(range.high ?? 200)" default: return "" } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionView.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionView.swift index 8a07698d..800bfe57 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionView.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionView.swift @@ -19,7 +19,7 @@ public class FilterLevelSectionView: UIView { private var isEdit = false let leftInputBox: InputBox = { - let box = InputBox(label: "범위", placeHodler: "1") + let box = InputBox(label: "범위", placeHodler: "0") box.textField.keyboardType = .numberPad return box }() @@ -48,7 +48,7 @@ public class FilterLevelSectionView: UIView { private let lowerLabel: UILabel = { let label = UILabel() - label.attributedText = .makeStyledString(font: .b_s_r, text: "1", color: .neutral500) + label.attributedText = .makeStyledString(font: .b_s_r, text: "0", color: .neutral500) return label }() @@ -66,8 +66,8 @@ public class FilterLevelSectionView: UIView { public var disposeBag = DisposeBag() - public init(initialLowerValue: CGFloat = 1, initialUpperValue: CGFloat = 200) { - self.slider = FilterSlider(minimumValue: 1, maximumValue: 200, initialLowerValue: initialLowerValue, initialUpperValue: initialUpperValue) + public init(initialLowerValue: CGFloat = 0, initialUpperValue: CGFloat = 200) { + self.slider = FilterSlider(minimumValue: 0, maximumValue: 200, initialLowerValue: initialLowerValue, initialUpperValue: initialUpperValue) super.init(frame: .zero) addViews() setupConstraints() @@ -134,8 +134,8 @@ public extension FilterLevelSectionView { .withUnretained(self) .subscribe { owner, value in guard !owner.isEdit else { return } - let lowValue = Int(value) - owner.leftInputBox.textField.text = value == owner.slider.minimumValue ? "1" : "\(lowValue)" + let lowValue = Int(value ?? 0) + owner.leftInputBox.textField.text = value == owner.slider.minimumValue ? nil : "\(lowValue)" } .disposed(by: disposeBag) @@ -143,8 +143,8 @@ public extension FilterLevelSectionView { .withUnretained(self) .subscribe { owner, value in guard !owner.isEdit else { return } - let upperValue = Int(value) - owner.rightInputBox.textField.text = value == owner.slider.minimumValue ? "200" : "\(upperValue)" + let upperValue = Int(value ?? 200) + owner.rightInputBox.textField.text = value == owner.slider.maximumValue ? nil : "\(upperValue)" } .disposed(by: disposeBag) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterSlider.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterSlider.swift index e1d8c4fc..c43bac3d 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterSlider.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterSlider.swift @@ -22,8 +22,8 @@ public class FilterSlider: UIControl { // MARK: - Value Relays private let minimumValueRelay = BehaviorRelay(value: 0) private let maximumValueRelay = BehaviorRelay(value: 200) - private let lowerValueRelay = BehaviorRelay(value: 0) - private let upperValueRelay = BehaviorRelay(value: 200) + private let lowerValueRelay = BehaviorRelay(value: nil) + private let upperValueRelay = BehaviorRelay(value: nil) // MARK: - Public properties public var minimumValue: CGFloat { @@ -36,14 +36,14 @@ public class FilterSlider: UIControl { set { maximumValueRelay.accept(newValue) } } - public var lowerValue: CGFloat { + public var lowerValue: CGFloat? { get { lowerValueRelay.value } - set { lowerValueRelay.accept(boundValue(newValue, lower: minimumValue, upper: maximumValue)) } + set { lowerValueRelay.accept(boundValue(newValue ?? minimumValue, lower: minimumValue, upper: maximumValue)) } } - public var upperValue: CGFloat { + public var upperValue: CGFloat? { get { upperValueRelay.value } - set { upperValueRelay.accept(boundValue(newValue, lower: minimumValue, upper: maximumValue)) } + set { upperValueRelay.accept(boundValue(newValue ?? maximumValue, lower: minimumValue, upper: maximumValue)) } } public let isThumbTracking: BehaviorRelay = .init(value: false) @@ -51,8 +51,8 @@ public class FilterSlider: UIControl { // MARK: - Observables public var minimumValueObservable: Observable { minimumValueRelay.asObservable() } public var maximumValueObservable: Observable { maximumValueRelay.asObservable() } - public var lowerValueObservable: Observable { lowerValueRelay.asObservable() } - public var upperValueObservable: Observable { upperValueRelay.asObservable() } + public var lowerValueObservable: Observable { lowerValueRelay.asObservable() } + public var upperValueObservable: Observable { upperValueRelay.asObservable() } // MARK: - UI Elements private let trackView = UIView() @@ -151,8 +151,8 @@ public class FilterSlider: UIControl { Observable.combineLatest(minimumValueRelay, maximumValueRelay) .subscribe(onNext: { [weak self] minVal, maxVal in guard let self = self else { return } - let clampedLower = self.boundValue(self.lowerValueRelay.value, lower: minVal, upper: maxVal) - let clampedUpper = self.boundValue(self.upperValueRelay.value, lower: minVal, upper: maxVal) + let clampedLower = self.boundValue(self.lowerValueRelay.value ?? self.minimumValue, lower: minVal, upper: maxVal) + let clampedUpper = self.boundValue(self.upperValueRelay.value ?? self.maximumValue, lower: minVal, upper: maxVal) if clampedLower != self.lowerValueRelay.value { self.lowerValueRelay.accept(clampedLower) } @@ -186,8 +186,8 @@ public class FilterSlider: UIControl { } private func updateTrackAndThumb(animated: Bool) { - let lowerX = position(for: lowerValueRelay.value) - let upperX = position(for: upperValueRelay.value) + let lowerX = position(for: lowerValueRelay.value ?? minimumValue) + let upperX = position(for: upperValueRelay.value ?? maximumValue) lowerThumbCenterX?.update(offset: lowerX - bounds.midX) upperThumbCenterX?.update(offset: upperX - bounds.midX) @@ -238,8 +238,8 @@ public class FilterSlider: UIControl { let deltaValue = (maximumValue - minimumValue) * deltaLocation / (bounds.width - Constant.thumbSize) previousLocation = location - var newLower = lowerValueRelay.value - var newUpper = upperValueRelay.value + var newLower = lowerValueRelay.value ?? minimumValue + var newUpper = upperValueRelay.value ?? maximumValue switch activeThumb { case .lower: From c0347c89c064bd9b44ec4ee564aed8ecf449285b Mon Sep 17 00:00:00 2001 From: JunYoung Date: Thu, 19 Feb 2026 02:03:08 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=ED=8C=8C=EC=8B=B1=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=98=B5=EC=85=94=EB=84=90=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DictionaryList/ParseItemFilterResultUseCaseImpl.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MLS/Domain/Domain/UseCaseImpl/DictionaryList/ParseItemFilterResultUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/DictionaryList/ParseItemFilterResultUseCaseImpl.swift index b2a9bf37..8bb273d4 100644 --- a/MLS/Domain/Domain/UseCaseImpl/DictionaryList/ParseItemFilterResultUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/DictionaryList/ParseItemFilterResultUseCaseImpl.swift @@ -34,7 +34,9 @@ public final class ParseItemFilterResultUseCaseImpl: ParseItemFilterResultUseCas } case "레벨": let levelText = value.replacingOccurrences(of: "레벨", with: "").trimmingCharacters(in: .whitespaces) - let parts = levelText.split(separator: "~").map { $0.trimmingCharacters(in: .whitespaces) } + let parts = levelText.split(separator: "~") + .map { $0.trimmingCharacters(in: .whitespaces) } + .map { $0.filter { $0.isNumber } } if let low = Int(parts.first ?? ""), let high = Int(parts.last ?? "") { criteria.startLevel = low criteria.endLevel = high From 2eff9c1cb78e95ebda4e89f7c65edb790dc0838c Mon Sep 17 00:00:00 2001 From: JunYoung Date: Thu, 19 Feb 2026 02:27:22 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=ED=95=84=ED=84=B0=20=EC=9E=AC?= =?UTF-8?q?=EC=A7=84=EC=9E=85=EC=8B=9C=20level=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ItemFilterBottomSheetViewController.swift | 13 ++++++++++--- .../Views/FilterLevelSectionCell.swift | 12 ++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift index 110dd2eb..9b414ee2 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift @@ -58,7 +58,7 @@ public final class ItemFilterBottomSheetViewController: BaseViewController, View enum FilterItem: Hashable { case job(String) - case level + case level(low: Int?, upper: Int?) case weapons(String) case projectiles(String) case armors(String) @@ -218,8 +218,9 @@ private extension ItemFilterBottomSheetViewController { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CheckBoxButtonListSmallCell.identifier, for: indexPath) as! CheckBoxButtonListSmallCell cell.inject(title: title) return cell - case .level: + case .level(let low, let upper): let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FilterLevelSectionCell.identifier, for: indexPath) as! FilterLevelSectionCell + cell.inject(input: .init(lowValue: low, highValue: upper)) guard let reactor = self.reactor else { return UICollectionViewCell() } let lowValue = cell.levelSectionView.slider.lowerValueObservable let highValue = cell.levelSectionView.slider.upperValueObservable @@ -264,7 +265,13 @@ private extension ItemFilterBottomSheetViewController { // 섹션별 아이템 추가 snapshot.appendItems(reactor.currentState.jobs.map { .job($0) }, toSection: .job) - snapshot.appendItems([.level], toSection: .level) + snapshot.appendItems( + [.level( + low: reactor.currentState.levelRange.low, + upper: reactor.currentState.levelRange.high + )], + toSection: .level + ) snapshot.appendItems(reactor.currentState.weapons.map { .weapons($0) }, toSection: .weapons) snapshot.appendItems(reactor.currentState.projectiles.map { .projectiles($0) }, toSection: .projectiles) snapshot.appendItems(reactor.currentState.armors.map { .armors($0) }, toSection: .armors) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionCell.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionCell.swift index 735b8093..ae04c6ee 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionCell.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/Views/FilterLevelSectionCell.swift @@ -45,3 +45,15 @@ private extension FilterLevelSectionCell { } } } + +extension FilterLevelSectionCell { + struct Input { + let lowValue: Int? + let highValue: Int? + } + + func inject(input: Input) { + levelSectionView.slider.lowerValue = input.lowValue.map { CGFloat($0) } + levelSectionView.slider.upperValue = input.highValue.map { CGFloat($0) } + } +} From e1734e1d562bca6cc881ad690729bfa7eb012131 Mon Sep 17 00:00:00 2001 From: JunYoung Date: Thu, 19 Feb 2026 03:09:23 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=EC=8B=9C=20=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift | 2 +- .../ItemFilterBottomSheetViewController.swift | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift index 32fa0fa1..7882922b 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetReactor.swift @@ -150,7 +150,7 @@ public final class ItemFilterBottomSheetReactor: Reactor { newState.levelRange = (low, high) case .resetFilters: newState.selectedItemIndexes = [] - newState.levelRange = (0, 200) + newState.levelRange = (nil, nil) newState.scrollCategories = ["무기 주문서", "방어구 주문서", "기타 주문서"] newState.weaponScrolls = [] newState.armorScrolls = [] diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift index 9b414ee2..4adbcbe5 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift @@ -414,6 +414,10 @@ extension ItemFilterBottomSheetViewController { // Reactor에 액션 전달 owner.reactor?.action.onNext(.resetFilters) + if let cell = owner.mainView.contentCollectionView.cellForItem(at: IndexPath(row: 0, section: 1)) as? FilterLevelSectionCell { + cell.levelSectionView.slider.lowerValue = 0 + cell.levelSectionView.slider.upperValue = 200 + } // UI에서 직접 deselect owner.mainView.contentCollectionView.indexPathsForSelectedItems?.forEach { owner.mainView.contentCollectionView.deselectItem(at: $0, animated: false) @@ -596,7 +600,6 @@ extension ItemFilterBottomSheetViewController: UICollectionViewDataSource { if let cell = owner.mainView.contentCollectionView.cellForItem(at: deselectedIndex) as? FilterLevelSectionCell { cell.levelSectionView.slider.lowerValue = 0 cell.levelSectionView.slider.upperValue = 200 - owner.reactor?.action.onNext(.changeLevelRange(low: 0, high: 200)) } owner.reactor?.action.onNext(.filterDeselected(indexPath: deselectedIndex)) case .weaponScrolls, .armorsScrolls, .etcScrolls: