From 115e19716cb79f57cefe7363d2332eb4316537c5 Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Wed, 29 Apr 2026 22:06:37 +0900 Subject: [PATCH 01/37] =?UTF-8?q?[Feat]=20#697=20-=20=20=EC=84=9C=EC=9E=AC?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=EC=BD=98=20Asset=EC=97=90=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../icon/mdiBookPlusOutline.imageset/Contents.json | 12 ++++++++++++ .../mdi_book-plus-outline.svg | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/Contents.json create mode 100644 WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/mdi_book-plus-outline.svg diff --git a/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/Contents.json b/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/Contents.json new file mode 100644 index 000000000..1b21a811f --- /dev/null +++ b/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "mdi_book-plus-outline.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/mdi_book-plus-outline.svg b/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/mdi_book-plus-outline.svg new file mode 100644 index 000000000..a48b386c5 --- /dev/null +++ b/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/mdi_book-plus-outline.svg @@ -0,0 +1,3 @@ + + + From 64829a0a87a9281f2134632697cf47c1ba1c36f4 Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Wed, 29 Apr 2026 22:15:51 +0900 Subject: [PATCH 02/37] =?UTF-8?q?[Feat]=20#697=20-=20=EC=84=9C=EC=9E=AC=20?= =?UTF-8?q?=ED=83=AD=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EC=84=9C=EC=9E=AC=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyLibraryNavigationView.swift | 21 ++++++++++++++----- .../MyLibraryView/MyLibraryView.swift | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift index 597536dc0..52dc0f876 100644 --- a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift +++ b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift @@ -13,8 +13,9 @@ import Then final class MyLibraryNavigationView: UIView { //MARK: - Components - + private let navigationTitle = UILabel() + let libraryAddButton = UIButton() // MARK: - Life Cycle @@ -39,21 +40,31 @@ final class MyLibraryNavigationView: UIView { $0.applyWSSFont(.headline1, with: StringLiterals.Navigation.Title.library) $0.textColor = .wssBlack } + + libraryAddButton.do { + $0.setImage(.mdiBookPlusOutline, for: .normal) + } } - + private func setHierarchy() { - addSubviews(navigationTitle) + addSubviews(navigationTitle, libraryAddButton) } - + private func setLayout() { self.snp.makeConstraints { $0.height.equalTo(56) } - + navigationTitle.snp.makeConstraints { $0.centerY.equalToSuperview() $0.leading.equalToSuperview().inset(20) } + + libraryAddButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview().inset(20) + $0.size.equalTo(24) + } } } diff --git a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryView.swift b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryView.swift index 3dfcade7f..faeed3808 100644 --- a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryView.swift +++ b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryView.swift @@ -14,7 +14,7 @@ final class MyLibraryView: UIView { //MARK: - Components - private let navigationView = MyLibraryNavigationView() + let navigationView = MyLibraryNavigationView() let headerView = MyLibraryHeaderView() let libraryCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()) let libraryTableView = UITableView(frame: .zero, style: .plain) From 285c18df98b0499750cc33542d5c9ac032cf0abc Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Thu, 30 Apr 2026 06:09:32 +0900 Subject: [PATCH 03/37] =?UTF-8?q?[Feat]=20#697=20-=20=EC=84=9C=EC=9E=AC=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20?= =?UTF-8?q?=EC=9D=BC=EB=B0=98=EA=B2=80=EC=83=89=ED=99=94=EB=A9=B4=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyLibraryViewController/MyLibraryViewController.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryViewController/MyLibraryViewController.swift b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryViewController/MyLibraryViewController.swift index fdbe99505..ecdc6bb6a 100644 --- a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryViewController/MyLibraryViewController.swift +++ b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryViewController/MyLibraryViewController.swift @@ -231,6 +231,13 @@ final class MyLibraryViewController: UIViewController { HapticManager.shared.generateSelectionFeedback() }) .disposed(by: disposeBag) + + rootView.navigationView.libraryAddButton.rx.tap + .asDriver() + .drive(with: self, onNext: { owner, _ in + owner.pushToNormalSearchViewController() + }) + .disposed(by: disposeBag) } } From 6acb27507fb11b0d4a2e769a3a457e15442a518a Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 13:52:55 +0900 Subject: [PATCH 04/37] =?UTF-8?q?[Fix]=20#697=20-=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contents.json | 0 .../mdi_book-plus-outline.svg | 0 .../MyLibraryAssistantView/MyLibraryNavigationView.swift | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename WSSiOS/Resource/Assets.xcassets/icon/{mdiBookPlusOutline.imageset => icBookPlus.imageset}/Contents.json (100%) rename WSSiOS/Resource/Assets.xcassets/icon/{mdiBookPlusOutline.imageset => icBookPlus.imageset}/mdi_book-plus-outline.svg (100%) diff --git a/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/Contents.json b/WSSiOS/Resource/Assets.xcassets/icon/icBookPlus.imageset/Contents.json similarity index 100% rename from WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/Contents.json rename to WSSiOS/Resource/Assets.xcassets/icon/icBookPlus.imageset/Contents.json diff --git a/WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/mdi_book-plus-outline.svg b/WSSiOS/Resource/Assets.xcassets/icon/icBookPlus.imageset/mdi_book-plus-outline.svg similarity index 100% rename from WSSiOS/Resource/Assets.xcassets/icon/mdiBookPlusOutline.imageset/mdi_book-plus-outline.svg rename to WSSiOS/Resource/Assets.xcassets/icon/icBookPlus.imageset/mdi_book-plus-outline.svg diff --git a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift index 52dc0f876..07ea4f1a4 100644 --- a/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift +++ b/WSSiOS/Source/Presentation/Library/MyLibrary/MyLibraryView/MyLibraryAssistantView/MyLibraryNavigationView.swift @@ -42,7 +42,7 @@ final class MyLibraryNavigationView: UIView { } libraryAddButton.do { - $0.setImage(.mdiBookPlusOutline, for: .normal) + $0.setImage(.icBookPlus, for: .normal) } } From 0fee1514d02aaf9cd898dbabe48a8d723489adbd Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Fri, 24 Apr 2026 01:31:34 +0900 Subject: [PATCH 05/37] =?UTF-8?q?[Chore]=20#694=20-=20=EC=9D=BC=EB=B0=98?= =?UTF-8?q?=20=EA=B2=80=EC=83=89=20=ED=94=8C=EB=A0=88=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=ED=99=80=EB=8D=94=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 키워드를 검색하세요 → 작품 제목, 작가를 검색하세요 --- WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift b/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift index d7b89bf6c..f485a26fc 100644 --- a/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift +++ b/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift @@ -120,7 +120,7 @@ extension StringLiterals { enum KeywordSearch { static let keywordSelect = "키워드 선택" - static let placeholder = "키워드를 검색하세요" + static let placeholder = "작품 제목, 작가를 검색하세요" static let searchResult = "검색결과" static let reset = "초기화" static let selectButtonText = "개 선택" From a3bf461b943dd8d9905ac6ef7610ab7ba16c460e Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Fri, 24 Apr 2026 03:40:58 +0900 Subject: [PATCH 06/37] =?UTF-8?q?[Chore]=20#694=20-=20SearchViewModel?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=86=8C=EC=86=8C=ED=94=BD=20Input/Output?= =?UTF-8?q?=20=EB=B0=8F=20API=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Base/WSSTabBarItem.swift | 2 +- .../SearchViewModel/SearchViewModel.swift | 50 ++----------------- 2 files changed, 4 insertions(+), 48 deletions(-) diff --git a/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift b/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift index e8c189978..790903faa 100644 --- a/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift +++ b/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift @@ -92,7 +92,7 @@ enum WSSTabBarItem: Int, CaseIterable { )) case .search: - return SearchViewController(viewModel: SearchViewModel(searchRepository: DefaultSearchRepository(searchService: DefaultSearchService()))) + return SearchViewController(viewModel: SearchViewModel()) case .feed: return FeedViewController() diff --git a/WSSiOS/Source/Presentation/Search/Search/SearchViewModel/SearchViewModel.swift b/WSSiOS/Source/Presentation/Search/Search/SearchViewModel/SearchViewModel.swift index 332bab51a..a1fe23b8f 100644 --- a/WSSiOS/Source/Presentation/Search/Search/SearchViewModel/SearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/Search/SearchViewModel/SearchViewModel.swift @@ -11,48 +11,32 @@ import RxSwift import RxCocoa import RxGesture + final class SearchViewModel: ViewModelType { //MARK: - Properties - - private let searchRepository: SearchRepository + private let disposeBag = DisposeBag() - + private let isLogined = APIConstants.isLogined //MARK: - Inputs struct Input { - let viewWillAppearEvent: Observable let searhBarDidTap: Observable let induceButtonDidTap: Observable - let sosoPickCellSelected: Observable let pushToDetailSearchResultNotification: Observable } //MARK: - Outputs struct Output { - var sosoPickList = BehaviorRelay<[SosoPickNovel]>(value: []) let pushToNormalSearchViewController = PublishRelay() let pushToDetailSearchViewController = PublishRelay() - let pushToNovelDetailViewController = PublishRelay() let pushToDetailSearchResultView = PublishRelay() let presentToInduceLoginView = PublishRelay() - let showLoadingView = PublishRelay() } - //MARK: - init - - init(searchRepository: SearchRepository) { - self.searchRepository = searchRepository - } - - //MARK: - API - - func getSosoPickNovels() -> Observable { - return searchRepository.getSosoPickNovels() - } } //MARK: - Methods @@ -61,22 +45,6 @@ extension SearchViewModel { func transform(from input: Input, disposeBag: DisposeBag) -> Output { let output = Output() - input.viewWillAppearEvent - .flatMapLatest { - self.getSosoPickNovels() - } - .do(onNext: { _ in - output.showLoadingView.accept(true) - }) - .subscribe(with: self, onNext: { owner, data in - output.sosoPickList.accept(data.sosoPicks) - output.showLoadingView.accept(false) - }, onError: { owner, error in - print(error) - output.showLoadingView.accept(false) - }) - .disposed(by: disposeBag) - input.searhBarDidTap .subscribe(onNext: { _ in AmplitudeManager.shared.track(AmplitudeEvent.Search.generalSearch) @@ -99,18 +67,6 @@ extension SearchViewModel { }) .disposed(by: disposeBag) - input.sosoPickCellSelected - .subscribe(onNext: { indexPath in - AmplitudeManager.shared.track(AmplitudeEvent.Search.sosoPick) - if self.isLogined { - let novelId = output.sosoPickList.value[indexPath.row].novelId - output.pushToNovelDetailViewController.accept(novelId) - } else { - output.presentToInduceLoginView.accept(()) - } - }) - .disposed(by: disposeBag) - input.pushToDetailSearchResultNotification .subscribe(with: self, onNext: { owner, notification in output.pushToDetailSearchResultView.accept(notification) From 5c41ec19b04c8afbd2cf3d78e4961249c64469bd Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Fri, 24 Apr 2026 03:41:02 +0900 Subject: [PATCH 07/37] =?UTF-8?q?[Chore]=20#694=20-=20SearchView/ViewContr?= =?UTF-8?q?oller=EC=97=90=EC=84=9C=20=EC=86=8C=EC=86=8C=ED=94=BD=20UI=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=94=EC=9D=B8=EB=94=A9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Search/Search/SearchView/SearchView.swift | 12 ++---- .../SearchViewController.swift | 37 +------------------ 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/WSSiOS/Source/Presentation/Search/Search/SearchView/SearchView.swift b/WSSiOS/Source/Presentation/Search/Search/SearchView/SearchView.swift index 64b8654d3..389733b84 100644 --- a/WSSiOS/Source/Presentation/Search/Search/SearchView/SearchView.swift +++ b/WSSiOS/Source/Presentation/Search/Search/SearchView/SearchView.swift @@ -20,8 +20,7 @@ final class SearchView: UIView { private let titleLabel = UILabel() let searchbarView = SearchBarView() let searchDetailInduceView = SearchDetailInduceView() - let sosopickView = SearchSosoPickView() - + private let loadingView = WSSLoadingView() // MARK: - Life Cycle @@ -61,8 +60,7 @@ final class SearchView: UIView { searchbarView, loadingView) scrollView.addSubview(contentView) - contentView.addSubviews(searchDetailInduceView, - sosopickView) + contentView.addSubviews(searchDetailInduceView) } private func setLayout() { @@ -94,11 +92,7 @@ final class SearchView: UIView { $0.top.equalToSuperview() $0.leading.trailing.equalToSuperview().inset(20) $0.height.equalTo(256) - } - - sosopickView.snp.makeConstraints { - $0.top.equalTo(searchDetailInduceView.snp.bottom).offset(24) - $0.leading.trailing.bottom.equalToSuperview() + $0.bottom.equalToSuperview().inset(24) } loadingView.snp.makeConstraints { diff --git a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift index 00e3e3f44..b74cc645e 100644 --- a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift @@ -18,8 +18,6 @@ final class SearchViewController: UIViewController { private let viewModel: SearchViewModel private let disposeBag = DisposeBag() - private let viewWillAppearEvent = PublishRelay() - //MARK: - Components private let rootView = SearchView() @@ -44,8 +42,7 @@ final class SearchViewController: UIViewController { showTabBar() setNavigationBar() - viewWillAppearEvent.accept(()) - + AmplitudeManager.shared.track(AmplitudeEvent.Search.search) } @@ -53,8 +50,6 @@ final class SearchViewController: UIViewController { super.viewDidLoad() setUI() - registerCell() - bindViewModel() } @@ -69,31 +64,15 @@ final class SearchViewController: UIViewController { } //MARK: - Bind - - private func registerCell() { - rootView.sosopickView.sosopickCollectionView.register( - SosoPickCollectionViewCell.self, - forCellWithReuseIdentifier: SosoPickCollectionViewCell.cellIdentifier) - } - + private func bindViewModel() { let input = SearchViewModel.Input( - viewWillAppearEvent: viewWillAppearEvent.asObservable(), searhBarDidTap: rootView.searchbarView.rx.tapGesture().when(.recognized).asObservable(), induceButtonDidTap: rootView.searchDetailInduceView.rx.tapGesture().when(.recognized).asObservable(), - sosoPickCellSelected: rootView.sosopickView.sosopickCollectionView.rx.itemSelected.asObservable(), pushToDetailSearchResultNotification: NotificationCenter.default.rx.notification(Notification.Name("PushToDetailSearchResult")).asObservable() ) let output = viewModel.transform(from: input, disposeBag: disposeBag) - output.sosoPickList - .bind(to: rootView.sosopickView.sosopickCollectionView.rx.items( - cellIdentifier: SosoPickCollectionViewCell.cellIdentifier, - cellType: SosoPickCollectionViewCell.self)) { row, element, cell in - cell.bindData(data: element) - } - .disposed(by: disposeBag) - output.pushToNormalSearchViewController .bind(with: self, onNext: { owner, _ in owner.pushToNormalSearchViewController() @@ -111,12 +90,6 @@ final class SearchViewController: UIViewController { }) .disposed(by: disposeBag) - output.pushToNovelDetailViewController - .bind(with: self, onNext: { owner, novelId in - owner.pushToNovelDetailViewController(novelId: novelId) - }) - .disposed(by: disposeBag) - output.pushToDetailSearchResultView .observe(on: MainScheduler.instance) .subscribe(with: self, onNext: { owner, notification in @@ -151,11 +124,5 @@ final class SearchViewController: UIViewController { }) .disposed(by: disposeBag) - output.showLoadingView - .observe(on: MainScheduler.instance) - .bind(with: self, onNext: { owner, isShow in - owner.rootView.showLoadingView(isShow: isShow) - }) - .disposed(by: disposeBag) } } From 726afd21e7d6096990f9715de2a3ae48a13a125a Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Fri, 24 Apr 2026 03:41:05 +0900 Subject: [PATCH 08/37] =?UTF-8?q?[Feat]=20#694=20-=20NormalSearchViewModel?= =?UTF-8?q?=EC=97=90=20=EC=86=8C=EC=86=8C=ED=94=BD=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NormalSearchViewModel.swift | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift index dae8aa171..129478b4b 100644 --- a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift @@ -29,9 +29,14 @@ final class NormalSearchViewModel: ViewModelType { private let isSearchTextFieldEditing = BehaviorRelay(value: false) private let normalSearchList = BehaviorRelay<[SearchNovel]>(value: []) private let normalSearchCellIndexPath = PublishRelay() - + // 로딩 private let showLoadingView = PublishRelay() + + // 소소픽 + private let isLogined = APIConstants.isLogined + private let sosoPickList = BehaviorRelay<[SosoPickNovel]>(value: []) + private let presentToInduceLoginView = PublishRelay() //MARK: - Inputs @@ -48,6 +53,7 @@ final class NormalSearchViewModel: ViewModelType { let normalSearchCellSelected: ControlEvent let reachedBottom: Observable let normalSearchCollectionViewSwipeGesture: Observable + let sosoPickCellSelected: ControlEvent } //MARK: - Outputs @@ -65,6 +71,8 @@ final class NormalSearchViewModel: ViewModelType { let isSearchTextFieldEditing: Observable let endEditing: Observable let showLoadingView: Observable + let sosoPickList: Observable<[SosoPickNovel]> + let presentToInduceLoginView: Observable } //MARK: - init @@ -78,6 +86,10 @@ final class NormalSearchViewModel: ViewModelType { //MARK: - API + private func getSosoPickNovels() -> Observable { + return searchRepository.getSosoPickNovels() + } + private func getNormalSearchList(query: String, page: Int) -> Observable { return searchRepository.getSearchNovels(query: query, page: page) .do( @@ -110,7 +122,27 @@ final class NormalSearchViewModel: ViewModelType { //MARK: - Methods func transform(from input: Input, disposeBag: DisposeBag) -> Output { - + + getSosoPickNovels() + .subscribe(with: self, onNext: { owner, data in + owner.sosoPickList.accept(data.sosoPicks) + }, onError: { _, error in + print(error.localizedDescription) + }) + .disposed(by: disposeBag) + + input.sosoPickCellSelected + .subscribe(with: self, onNext: { owner, indexPath in + AmplitudeManager.shared.track(AmplitudeEvent.Search.sosoPick) + if owner.isLogined { + let novelId = owner.sosoPickList.value[indexPath.row].novelId + owner.pushToNovelDetailViewController.accept(novelId) + } else { + owner.presentToInduceLoginView.accept(()) + } + }) + .disposed(by: disposeBag) + let searchRequest = Observable.merge(input.returnKeyDidTap.asObservable(), input.searchButtonDidTap.asObservable()) .withLatestFrom(input.searchTextUpdated) @@ -196,6 +228,8 @@ final class NormalSearchViewModel: ViewModelType { pushToNovelDetailViewController: pushToNovelDetailViewController.asObservable(), isSearchTextFieldEditing: isSearchTextFieldEditing.asObservable(), endEditing: endEditing, - showLoadingView: showLoadingView.asObservable()) + showLoadingView: showLoadingView.asObservable(), + sosoPickList: sosoPickList.asObservable(), + presentToInduceLoginView: presentToInduceLoginView.asObservable()) } } From bac52bb4dc1281d13f51c736275e7ea85899d85b Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Fri, 24 Apr 2026 03:41:08 +0900 Subject: [PATCH 09/37] =?UTF-8?q?[Feat]=20#694=20-=20NormalSearchView?= =?UTF-8?q?=EC=97=90=20sosoPickView=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NormalSearchView/NormalSearchView.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalSearchView.swift b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalSearchView.swift index e2188e22a..7a4dead22 100644 --- a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalSearchView.swift +++ b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalSearchView.swift @@ -15,9 +15,10 @@ final class NormalSearchView: UIView { //MARK: - Components let headerView = NormalSearchHeaderView() + let sosoPickView = SearchSosoPickView() let resultView = NormalSearchResultView() let emptyView = NormalSearchEmptyView() - + let loadingView = WSSLoadingView() // MARK: - Life Cycle @@ -44,6 +45,7 @@ final class NormalSearchView: UIView { private func setHierarchy() { self.addSubviews(headerView, + sosoPickView, resultView, emptyView, loadingView) @@ -54,7 +56,12 @@ final class NormalSearchView: UIView { $0.top.equalTo(self.safeAreaLayoutGuide.snp.top).inset(1) $0.leading.trailing.equalToSuperview() } - + + sosoPickView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom).offset(16) + $0.leading.trailing.equalToSuperview() + } + resultView.snp.makeConstraints { $0.top.equalTo(headerView.snp.bottom) $0.horizontalEdges.bottom.equalToSuperview() From 24052ad5fd981ffc7b1b8261b5cb75a901b1c71e Mon Sep 17 00:00:00 2001 From: onesunny2 Date: Fri, 24 Apr 2026 03:41:11 +0900 Subject: [PATCH 10/37] =?UTF-8?q?[Feat]=20#694=20-=20NormalSearchViewContr?= =?UTF-8?q?oller=EC=97=90=20=EC=86=8C=EC=86=8C=ED=94=BD=20=EB=B0=94?= =?UTF-8?q?=EC=9D=B8=EB=94=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NormalSearchViewController.swift | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewController/NormalSearchViewController.swift b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewController/NormalSearchViewController.swift index b41603502..31fbc2042 100644 --- a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewController/NormalSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewController/NormalSearchViewController.swift @@ -86,6 +86,9 @@ final class NormalSearchViewController: UIViewController, UIScrollViewDelegate { rootView.resultView.normalSearchCollectionView.register( NormalSearchCollectionViewCell.self, forCellWithReuseIdentifier: NormalSearchCollectionViewCell.cellIdentifier) + rootView.sosoPickView.sosopickCollectionView.register( + SosoPickCollectionViewCell.self, + forCellWithReuseIdentifier: SosoPickCollectionViewCell.cellIdentifier) } private func setDelegate() { @@ -114,7 +117,8 @@ final class NormalSearchViewController: UIViewController, UIScrollViewDelegate { normalSearchCollectionViewContentSize: rootView.resultView.normalSearchCollectionView.rx.observe(CGSize.self, "contentSize"), normalSearchCellSelected: rootView.resultView.normalSearchCollectionView.rx.itemSelected, reachedBottom: reachedBottom, - normalSearchCollectionViewSwipeGesture: collectionViewSwipeGesture) + normalSearchCollectionViewSwipeGesture: collectionViewSwipeGesture, + sosoPickCellSelected: rootView.sosoPickView.sosopickCollectionView.rx.itemSelected) let output = viewModel.transform(from: input, disposeBag: disposeBag) output.resultCount @@ -140,16 +144,19 @@ final class NormalSearchViewController: UIViewController, UIScrollViewDelegate { owner.rootView.emptyView.isHidden = true owner.rootView.resultView.isHidden = true owner.rootView.resultView.resultCountView.isHidden = true + owner.rootView.sosoPickView.isHidden = false } - else if novels.isEmpty && !(owner.rootView.headerView.searchTextField.text == "") { + else if novels.isEmpty { owner.rootView.emptyView.isHidden = false owner.rootView.resultView.isHidden = true owner.rootView.resultView.resultCountView.isHidden = true + owner.rootView.sosoPickView.isHidden = true } else { owner.rootView.emptyView.isHidden = true owner.rootView.resultView.isHidden = false owner.rootView.resultView.resultCountView.isHidden = false + owner.rootView.sosoPickView.isHidden = true } }) .disposed(by: disposeBag) @@ -218,6 +225,22 @@ final class NormalSearchViewController: UIViewController, UIScrollViewDelegate { owner.rootView.showLoadingView(isShow: isShow) }) .disposed(by: disposeBag) + + output.sosoPickList + .observe(on: MainScheduler.instance) + .bind(to: rootView.sosoPickView.sosopickCollectionView.rx.items( + cellIdentifier: SosoPickCollectionViewCell.cellIdentifier, + cellType: SosoPickCollectionViewCell.self)) { _, element, cell in + cell.bindData(data: element) + } + .disposed(by: disposeBag) + + output.presentToInduceLoginView + .observe(on: MainScheduler.instance) + .subscribe(with: self, onNext: { owner, _ in + owner.presentInduceLoginViewController() + }) + .disposed(by: disposeBag) } private func bindAction() { From 2ec9be7ab6f0d2d3f14c455dbc8dc756fb25b653 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 13:59:25 +0900 Subject: [PATCH 11/37] =?UTF-8?q?[Fix]=20#694=20-=20StringLiterals=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift | 2 +- .../NormalAssistantView/NormalSearchHeaderView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift b/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift index f485a26fc..d7b89bf6c 100644 --- a/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift +++ b/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift @@ -120,7 +120,7 @@ extension StringLiterals { enum KeywordSearch { static let keywordSelect = "키워드 선택" - static let placeholder = "작품 제목, 작가를 검색하세요" + static let placeholder = "키워드를 검색하세요" static let searchResult = "검색결과" static let reset = "초기화" static let selectButtonText = "개 선택" diff --git a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalAssistantView/NormalSearchHeaderView.swift b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalAssistantView/NormalSearchHeaderView.swift index cbfa52c97..9b32d790c 100644 --- a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalAssistantView/NormalSearchHeaderView.swift +++ b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchView/NormalAssistantView/NormalSearchHeaderView.swift @@ -48,7 +48,7 @@ final class NormalSearchHeaderView: UIView { $0.tintColor = .wssBlack $0.backgroundColor = .wssGray50 $0.textColor = .wssBlack - $0.placeholder = StringLiterals.NovelReview.KeywordSearch.placeholder + $0.placeholder = StringLiterals.Search.searchbar $0.font = .Body4 $0.layer.cornerRadius = 14 $0.layer.borderColor = UIColor.wssGray70.cgColor From f722177b02f59230e869350bae48d443c59a0fc0 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 14:07:58 +0900 Subject: [PATCH 12/37] =?UTF-8?q?[Fix[=20#694=20-=20=EB=A7=A4=EB=A0=A5?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=ED=95=84=EB=A0=A5=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=EC=82=AC=ED=95=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../icAttractiveWritingSkill.imageset/Contents.json | 12 ++++++++++++ .../icAttractiveWritingSkill.svg | 10 ++++++++++ WSSiOS/Source/Data/Base/AttractivePoint.swift | 3 +++ 3 files changed, 25 insertions(+) create mode 100644 WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/Contents.json create mode 100644 WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/icAttractiveWritingSkill.svg diff --git a/WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/Contents.json b/WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/Contents.json new file mode 100644 index 000000000..981a43f40 --- /dev/null +++ b/WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icAttractiveWritingSkill.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/icAttractiveWritingSkill.svg b/WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/icAttractiveWritingSkill.svg new file mode 100644 index 000000000..56c18567b --- /dev/null +++ b/WSSiOS/Resource/Assets.xcassets/icon/AttractivePoint/icAttractiveWritingSkill.imageset/icAttractiveWritingSkill.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/WSSiOS/Source/Data/Base/AttractivePoint.swift b/WSSiOS/Source/Data/Base/AttractivePoint.swift index 2a798b447..c779abccc 100644 --- a/WSSiOS/Source/Data/Base/AttractivePoint.swift +++ b/WSSiOS/Source/Data/Base/AttractivePoint.swift @@ -10,6 +10,7 @@ import UIKit enum AttractivePoint: String, CaseIterable, Codable { case worldview = "worldview" case material = "material" + case writingSkill = "writingskill" case character = "character" case relationship = "relationship" case vibe = "vibe" @@ -18,6 +19,7 @@ enum AttractivePoint: String, CaseIterable, Codable { switch self { case .worldview: "세계관" case .material: "소재" + case .writingSkill: "필력" case .character: "캐릭터" case .relationship: "관계" case .vibe: "분위기" @@ -28,6 +30,7 @@ enum AttractivePoint: String, CaseIterable, Codable { switch self { case .worldview: .icAttractiveWorldview case .material: .icAttractiveMaterial + case .writingSkill: .icAttractiveWritingSkill case .character: .icAttractiveCharacter case .relationship: .icAttractiveRelationship case .vibe: .icAttractiveVibe From 81064ba924c8dc386e45ac77fdbb91b5ef8517a6 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 14:50:50 +0900 Subject: [PATCH 13/37] =?UTF-8?q?[Fix]=20#693=20-=20=ED=94=BC=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=8B=9C=20=EC=97=B0=EA=B2=B0=EC=9E=91=ED=92=88=20?= =?UTF-8?q?UI=20=EC=83=81=EB=8B=A8=EC=97=90=20=EC=9E=88=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20stackView=20=EC=9C=84=EC=B9=98=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/FeedEdit/FeedEditView/FeedEditView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WSSiOS/Source/Presentation/FeedEdit/FeedEditView/FeedEditView.swift b/WSSiOS/Source/Presentation/FeedEdit/FeedEditView/FeedEditView.swift index 121d32936..00edae607 100644 --- a/WSSiOS/Source/Presentation/FeedEdit/FeedEditView/FeedEditView.swift +++ b/WSSiOS/Source/Presentation/FeedEdit/FeedEditView/FeedEditView.swift @@ -129,7 +129,7 @@ final class FeedEditView: UIView { func showAddImages(hasImage: Bool) { if hasImage { - stackView.insertArrangedSubview(feedEditAddImageView, at: 2) + stackView.insertArrangedSubview(feedEditAddImageView, at: 1) stackView.setCustomSpacing(14, after: feedEditContentView) } else { feedEditAddImageView.removeFromSuperview() From 2e81a6338866a2bda229b53bcf3903431db6857f Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 15:29:39 +0900 Subject: [PATCH 14/37] =?UTF-8?q?[Feat]=20#695=20-=20HomeView=20=EB=82=B4?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=ED=83=90=EC=83=89=20=EB=B0=B0=EB=84=88?= =?UTF-8?q?=EB=B7=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contents.json | 23 +++++ .../imgHomeDetailSearch.png | Bin 0 -> 16611 bytes .../imgHomeDetailSearch@2x.png | Bin 0 -> 49792 bytes .../imgHomeDetailSearch@3x.png | Bin 0 -> 102618 bytes .../Strings/StringLiterals+Home.swift | 2 + .../HomeInduceDetailSearchView.swift | 92 ++++++++++++++++++ .../Home/Home/HomeView/HomeView.swift | 9 +- 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/Contents.json create mode 100644 WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/imgHomeDetailSearch.png create mode 100644 WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/imgHomeDetailSearch@2x.png create mode 100644 WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/imgHomeDetailSearch@3x.png create mode 100644 WSSiOS/Source/Presentation/Home/Home/HomeView/HomeAssistantView/HomeInduceDetailSearchView.swift diff --git a/WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/Contents.json b/WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/Contents.json new file mode 100644 index 000000000..dc157e09b --- /dev/null +++ b/WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "imgHomeDetailSearch.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "imgHomeDetailSearch@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "imgHomeDetailSearch@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/imgHomeDetailSearch.png b/WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/imgHomeDetailSearch.png new file mode 100644 index 0000000000000000000000000000000000000000..b0a67856e7eda38ac48e5759c9e3738489694b30 GIT binary patch literal 16611 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 P00000NkvXXu0mjfL?{zH literal 0 HcmV?d00001 diff --git a/WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/imgHomeDetailSearch@3x.png b/WSSiOS/Resource/Assets.xcassets/Image/imgHomeDetailSearch.imageset/imgHomeDetailSearch@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d26e8b69f2a4f0d0124912e504b090a9e4afbca4 GIT binary patch literal 102618 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

={(|QG|5( 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 Date: Wed, 29 Apr 2026 15:47:07 +0900 Subject: [PATCH 15/37] =?UTF-8?q?[Feat]=20#695=20-=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=83=90=EC=83=89=20=EB=B7=B0=20=EC=A0=84=ED=99=98=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EB=B0=94=ED=85=80=EC=8B=9C=ED=8A=B8=20->=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resource/Extensions/UIViewController+.swift | 17 ++++++++++------- .../HomeViewController/HomeViewController.swift | 16 +++++++++++++++- .../Home/Home/HomeViewModel/HomeViewModel.swift | 10 ++++++++++ .../DetailSearchResultViewController.swift | 2 +- .../SearchViewController.swift | 2 +- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/WSSiOS/Resource/Extensions/UIViewController+.swift b/WSSiOS/Resource/Extensions/UIViewController+.swift index dca49f361..2e85eb052 100644 --- a/WSSiOS/Resource/Extensions/UIViewController+.swift +++ b/WSSiOS/Resource/Extensions/UIViewController+.swift @@ -306,9 +306,9 @@ extension UIViewController { self.navigationController?.pushViewController(viewController, animated: true) } - func presentToDetailSearchViewController(selectedKeywordList: [KeywordData], - previousViewInfo: PreviousViewType, - selectedFilteredQuery: SearchFilterQuery) { + func pushToDetailSearchViewController(selectedKeywordList: [KeywordData], + previousViewInfo: PreviousViewType, + selectedFilteredQuery: SearchFilterQuery) { let detailSearchViewController = DetailSearchViewController( viewModel: DetailSearchViewModel( keywordRepository: DefaultKeywordRepository( @@ -316,7 +316,10 @@ extension UIViewController { selectedKeywordList: selectedKeywordList, previousViewInfo: previousViewInfo, selectedFilteredQuery: selectedFilteredQuery)) - self.presentModalViewController(detailSearchViewController) + detailSearchViewController.navigationController?.isNavigationBarHidden = true + detailSearchViewController.hidesBottomBarWhenPushed = true + self.navigationController?.pushViewController(detailSearchViewController, + animated: true) } func presentInduceLoginViewController() { @@ -363,8 +366,8 @@ extension UIViewController { func pushToChangeUserInfoViewController() { let viewController = MyPageChangeUserInfoViewController( - userRepository: DefaultUserInfoRepository( - userService: DefaultUserService() + userRepository: DefaultUserInfoRepository( + userService: DefaultUserService() ) ) viewController.hidesBottomBarWhenPushed = true @@ -373,7 +376,7 @@ extension UIViewController { func pushToLibraryViewController(userId: Int, pageIndex: Int = 0) { let viewController = UserLibraryViewController(userId: userId) - + viewController.setPageIndex(target: pageIndex) viewController.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(viewController, animated: true) diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift index 4f8af0406..d5c411c1b 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift @@ -107,7 +107,8 @@ final class HomeViewController: UIViewController { announcementButtonDidTap: rootView.headerView.announcementButton.rx.tap, registerInterestNovelButtonTapped: rootView.interestView.unregisterView.registerButton.rx.tap, setPreferredGenresButtonTapped: rootView.tasteRecommendView.unregisterView.registerButton.rx.tap, - searchBarViewDidTap: rootView.searchBarView.rx.tapGesture().when(.recognized).asObservable() + searchBarViewDidTap: rootView.searchBarView.rx.tapGesture().when(.recognized).asObservable(), + indunceDetailSearchViewDidTap: rootView.induceDetailSearchView.rx.tapGesture().when(.recognized) ) let output = viewModel.transform(from: input, disposeBag: disposeBag) @@ -220,6 +221,19 @@ final class HomeViewController: UIViewController { }) .disposed(by: disposeBag) + output.pushToDetailSearchViewController + .bind(with: self, onNext: { owner, _ in + owner.pushToDetailSearchViewController( + selectedKeywordList: [], + previousViewInfo: .search, + selectedFilteredQuery: SearchFilterQuery(keywords: [], + genres: [], + isCompleted: nil, + novelRating: nil) + ) + }) + .disposed(by: disposeBag) + output.showLoadingView .observe(on: MainScheduler.instance) .bind(with: self, onNext: { owner, isShow in diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift b/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift index 79defa150..988670990 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift @@ -42,6 +42,7 @@ final class HomeViewModel: ViewModelType { private let pushToNovelDetailViewController = PublishRelay() private let pushToAnnouncementViewController = PublishRelay() + private let pushToDetailSearchViewController = PublishRelay() let showInduceLoginModalView = PublishRelay() private let showLoadingView = PublishRelay() @@ -62,6 +63,7 @@ final class HomeViewModel: ViewModelType { let registerInterestNovelButtonTapped: ControlEvent let setPreferredGenresButtonTapped: ControlEvent let searchBarViewDidTap: Observable + let indunceDetailSearchViewDidTap: Observable } //MARK: - Outputs @@ -84,6 +86,7 @@ final class HomeViewModel: ViewModelType { let pushToNovelDetailViewController: Observable let pushToAnnouncementViewController: Observable + let pushToDetailSearchViewController: Observable let showInduceLoginModalView: Observable let showLoadingView: Observable let showUpdateVersionAlertView: Observable @@ -191,6 +194,12 @@ extension HomeViewModel { }) .disposed(by: disposeBag) + input.indunceDetailSearchViewDidTap + .subscribe(with: self, onNext: { owner, _ in + owner.pushToDetailSearchViewController.accept(()) + }) + .disposed(by: disposeBag) + input.todayPopularCellSelected .subscribe(with: self, onNext: { owner, indexPath in AmplitudeManager.shared.track(AmplitudeEvent.Home.homeTodayRanking) @@ -267,6 +276,7 @@ extension HomeViewModel { pushToMyPageEditViewController: pushToMyPageViewController.asObservable(), pushToNovelDetailViewController: pushToNovelDetailViewController.asObservable(), pushToAnnouncementViewController: pushToAnnouncementViewController.asObservable(), + pushToDetailSearchViewController: pushToDetailSearchViewController.asObservable(), showInduceLoginModalView: showInduceLoginModalView.asObservable(), showLoadingView: showLoadingView.asObservable(), showUpdateVersionAlertView: showUpdateVersionAlertView.asObservable(), diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift index 62a9df8ba..7d8ca7129 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift @@ -124,7 +124,7 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele output.presentDetailSearchModal .subscribe(with: self, onNext: { owner, data in - owner.presentToDetailSearchViewController(selectedKeywordList: data.keywords, + owner.pushToDetailSearchViewController(selectedKeywordList: data.keywords, previousViewInfo: .resultSearchBar, selectedFilteredQuery: data) }) diff --git a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift index b74cc645e..41bdd3d55 100644 --- a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift @@ -81,7 +81,7 @@ final class SearchViewController: UIViewController { output.pushToDetailSearchViewController .bind(with: self, onNext: { owner, _ in - owner.presentToDetailSearchViewController(selectedKeywordList: [], + owner.pushToDetailSearchViewController(selectedKeywordList: [], previousViewInfo: .search, selectedFilteredQuery: SearchFilterQuery(keywords: [], genres: [], From 98e72989d18b22e926850b51592360bbfdfc2dbb Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 18:58:12 +0900 Subject: [PATCH 16/37] =?UTF-8?q?[Feat]=20#695=20-=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=83=90=EC=83=89=20=EB=82=B4=20headerView=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20CTAButton=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailSearchHeaderView.swift | 64 +++++++++++-- .../DetailSearchView/DetailSearchView.swift | 96 ++++++++----------- .../DetailSearchViewController.swift | 10 +- .../DetailSearchViewModel.swift | 38 ++++---- 4 files changed, 123 insertions(+), 85 deletions(-) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift index 503042cc9..14359aa71 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift @@ -17,6 +17,7 @@ enum DetailSearchTab { final class DetailSearchHeaderView: UIView { //MARK: - UI Components + let backButton = UIButton() let infoLabel = UILabel() let newInfoImageView = UIImageView() @@ -24,6 +25,10 @@ final class DetailSearchHeaderView: UIView { let newKeywordImageView = UIImageView() let underLineView = UIView() + let resetStackView = UIStackView() + private let resetImageView = UIImageView() + private let resetLabel = UILabel() + //MARK: - Life Cycle override init(frame: CGRect) { @@ -40,6 +45,15 @@ final class DetailSearchHeaderView: UIView { } private func setUI() { + backButton.do { + $0.setImage( + .icNavigateLeft + .withRenderingMode(.alwaysOriginal) + .withTintColor(.wssBlack), + for: .normal + ) + } + infoLabel.do { $0.applyWSSFont(.title1, with: StringLiterals.DetailSearch.info) $0.textColor = .wssPrimary100 @@ -58,19 +72,48 @@ final class DetailSearchHeaderView: UIView { $0.image = .icSearchNew.withTintColor(.wssPrimary100) $0.isHidden = true } + + resetStackView.do { + $0.axis = .horizontal + $0.spacing = 4 + } + + resetImageView.do { + $0.image = .icReload + .withRenderingMode(.alwaysOriginal) + .withTintColor(.wssGray300) + $0.contentMode = .scaleAspectFit + } + + resetLabel.do { + $0.applyWSSFont(.title2, with: StringLiterals.DetailSearch.reload) + $0.textColor = .wssGray300 + } } private func setHierarchy() { - self.addSubviews(infoLabel, + self.addSubviews(backButton, + infoLabel, newInfoImageView, keywordLabel, newKeywordImageView, - underLineView) + underLineView, + resetStackView) + + resetStackView.addArrangedSubviews(resetImageView, + resetLabel) } private func setLayout() { + backButton.snp.makeConstraints { + $0.size.equalTo(44) + $0.leading.equalToSuperview().inset(6) + $0.top.bottom.equalToSuperview() + } + infoLabel.snp.makeConstraints { - $0.top.leading.equalToSuperview() + $0.top.equalToSuperview().inset(7) + $0.leading.equalTo(backButton.snp.trailing).offset(14) } newInfoImageView.snp.makeConstraints { @@ -82,7 +125,6 @@ final class DetailSearchHeaderView: UIView { keywordLabel.snp.makeConstraints { $0.top.equalTo(infoLabel.snp.top) $0.leading.equalTo(infoLabel.snp.trailing).offset(29.5) - $0.trailing.equalToSuperview() } newKeywordImageView.snp.makeConstraints { @@ -95,7 +137,15 @@ final class DetailSearchHeaderView: UIView { $0.top.equalTo(keywordLabel.snp.bottom).offset(6) $0.horizontalEdges.equalTo(infoLabel.snp.horizontalEdges) $0.height.equalTo(2) - $0.bottom.equalToSuperview() + $0.bottom.equalToSuperview().inset(4) + } + + resetStackView.snp.makeConstraints { + $0.trailing.centerY.equalToSuperview() + + resetImageView.snp.makeConstraints { + $0.size.equalTo(14) + } } } @@ -121,14 +171,14 @@ final class DetailSearchHeaderView: UIView { $0.top.equalTo(self.infoLabel.snp.bottom).offset(6) $0.horizontalEdges.equalTo(self.infoLabel.snp.horizontalEdges) $0.height.equalTo(2) - $0.bottom.equalToSuperview() + $0.bottom.equalToSuperview().inset(4) } case .keyword: self.underLineView.snp.remakeConstraints { $0.top.equalTo(self.keywordLabel.snp.bottom).offset(6) $0.horizontalEdges.equalTo(self.keywordLabel.snp.horizontalEdges) $0.height.equalTo(2) - $0.bottom.equalToSuperview() + $0.bottom.equalToSuperview().inset(4) } } self.layoutIfNeeded() diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchView.swift index 1ac3f2e4f..810bc2dfd 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchView.swift @@ -14,17 +14,13 @@ final class DetailSearchView: UIView { //MARK: - UI Components - private let backgroundView = UIView() - let cancelModalButton = UIButton() - let detailSearchHeaderView = DetailSearchHeaderView() let detailSearchInfoView = DetailSearchInfoView() let detailSearchKeywordView = DetailSearchKeywordView() - let detailSearchBottomView = WSSSearchBottomActionView() - - // Home Indicator 배경 - private let backgroundBottomView = UIView() + let detailSearchButton = UIButton() + private let detailSearchButtonLabel = UILabel() + //MARK: - Life Cycle override init(frame: CGRect) { @@ -39,69 +35,59 @@ final class DetailSearchView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + private func setUI() { - backgroundView.do { + self.do { $0.backgroundColor = .wssWhite - $0.layer.cornerRadius = 15 - $0.layer.maskedCorners = [.layerMinXMinYCorner, - .layerMaxXMinYCorner] } - cancelModalButton.do { - $0.setImage(.icCancelModal.withRenderingMode(.alwaysOriginal).withTintColor(.wssGray300), for: .normal) + detailSearchButton.do { + $0.backgroundColor = .wssPrimary100 + $0.layer.cornerRadius = 14 + $0.isEnabled = true } - backgroundBottomView.do { - $0.backgroundColor = .wssWhite + detailSearchButtonLabel.do { + $0.applyWSSFont(.title1, with: "작품 찾기") + $0.textColor = .wssWhite } } private func setHierarchy() { - backgroundView.addSubviews(cancelModalButton, - detailSearchHeaderView, - detailSearchInfoView, - detailSearchKeywordView, - detailSearchBottomView) - self.addSubviews(backgroundView, - backgroundBottomView) + self.addSubviews(detailSearchHeaderView, + detailSearchInfoView, + detailSearchKeywordView, + detailSearchButton) + detailSearchButton.addSubview(detailSearchButtonLabel) } private func setLayout() { - backgroundView.snp.makeConstraints { - $0.top.equalToSuperview().inset(82) - $0.leading.trailing.bottom.equalToSuperview() - - cancelModalButton.snp.makeConstraints { - $0.size.equalTo(25) - $0.top.trailing.equalToSuperview().inset(20) - } - - detailSearchHeaderView.snp.makeConstraints { - $0.top.leading.equalToSuperview().inset(34) - } - - detailSearchKeywordView.snp.makeConstraints { - $0.top.equalTo(detailSearchHeaderView.snp.bottom).offset(UIScreen.isSE ? 15 : 30) - $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(detailSearchBottomView.snp.top) - } - - detailSearchInfoView.snp.makeConstraints { - $0.top.equalTo(detailSearchHeaderView.snp.bottom).offset(UIScreen.isSE ? 15 : 30) - $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(detailSearchBottomView.snp.top) - } - - detailSearchBottomView.snp.makeConstraints { - $0.bottom.equalTo(safeAreaLayoutGuide.snp.bottom) - $0.leading.trailing.equalToSuperview() - } + detailSearchHeaderView.snp.makeConstraints { + $0.top.equalTo(safeAreaLayoutGuide.snp.top) + $0.leading.equalToSuperview().inset(6) + $0.trailing.equalToSuperview().inset(16) + } + + detailSearchKeywordView.snp.makeConstraints { + $0.top.equalTo(detailSearchHeaderView.snp.bottom).offset(UIScreen.isSE ? 15 : 30) + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(detailSearchButton.snp.top).offset(-10) + } + + detailSearchInfoView.snp.makeConstraints { + $0.top.equalTo(detailSearchHeaderView.snp.bottom).offset(UIScreen.isSE ? 15 : 30) + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(detailSearchButton.snp.top).offset(-10) + } + + detailSearchButton.snp.makeConstraints { + $0.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-10) + $0.leading.trailing.equalToSuperview().inset(16) } - backgroundBottomView.snp.makeConstraints { - $0.top.equalTo(safeAreaLayoutGuide.snp.bottomMargin) - $0.horizontalEdges.bottom.equalToSuperview() + detailSearchButtonLabel.snp.makeConstraints { + $0.verticalEdges.equalTo(detailSearchButton).inset(14) + $0.centerX.equalToSuperview() } } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index a42734298..874864f6b 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -102,11 +102,11 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { let input = DetailSearchViewModel.Input( viewDidLoadEvent: viewDidLoadEvent.asObservable(), - closeButtonDidTap: rootView.cancelModalButton.rx.tap, + closeButtonDidTap: rootView.detailSearchHeaderView.backButton.rx.tap, infoTabDidTap: rootView.detailSearchHeaderView.infoLabel.rx.tapGesture().when(.recognized).asObservable(), keywordTabDidTap: rootView.detailSearchHeaderView.keywordLabel.rx.tapGesture().when(.recognized).asObservable(), - resetButtonDidTap: rootView.detailSearchBottomView.resetButton.rx.tap, - searchNovelButtonDidTap: rootView.detailSearchBottomView.searchButton.rx.tap, + resetViewDidTap: rootView.detailSearchHeaderView.resetStackView.rx.tapGesture().when(.recognized), + searchNovelButtonDidTap: rootView.detailSearchButton.rx.tap, updateDetailSearchResultData: NotificationCenter.default.rx.notification(Notification.Name("PushToUpateDetailSearchResult")).asObservable(), genreColletionViewItemSelected: rootView.detailSearchInfoView.genreCollectionView.rx.itemSelected.asObservable(), genreColletionViewItemDeselected: rootView.detailSearchInfoView.genreCollectionView.rx.itemDeselected.asObservable(), @@ -128,9 +128,9 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { let output = viewModel.transform(from: input, disposeBag: disposeBag) // 전체 - output.dismissModalViewController + output.popViewController .bind(with: self, onNext: { owner, _ in - owner.dismissModalViewController() + owner.popToLastViewController() }) .disposed(by: disposeBag) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index 2f7ef6705..afd653b96 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -55,7 +55,7 @@ final class DetailSearchViewModel: ViewModelType { let closeButtonDidTap: ControlEvent let infoTabDidTap: Observable let keywordTabDidTap: Observable - let resetButtonDidTap: ControlEvent + let resetViewDidTap: Observable let searchNovelButtonDidTap: ControlEvent let updateDetailSearchResultData: Observable @@ -83,7 +83,7 @@ final class DetailSearchViewModel: ViewModelType { struct Output { // 전체 - let dismissModalViewController: Observable + let popViewController: Observable let selectedTab: Driver let showInfoNewImageView: Observable let showKeywordNewImageView: Observable @@ -162,22 +162,24 @@ final class DetailSearchViewModel: ViewModelType { }) .disposed(by: disposeBag) - input.resetButtonDidTap + input.resetViewDidTap .subscribe(with: self, onNext: { owner, _ in - // 정보뷰 - owner.selectedGenreList = [] - owner.selectedGenreListData.accept(owner.selectedGenreList) - owner.resetSelectedInfoData.accept(()) - owner.selectedCompletedStatus.accept(nil) - owner.selectedNovelRatingStatus.accept(nil) - - // 키워드뷰 - owner.selectedKeywordList = [] - owner.selectedKeywordListData.accept(owner.selectedKeywordList) - owner.enteredText.accept("") - owner.keywordSearchResultListData.accept([]) - owner.showEmptyView.accept(false) - owner.showCategoryListView.accept(true) + if owner.selectedTab.value == .info { + // 정보뷰 + owner.selectedGenreList = [] + owner.selectedGenreListData.accept(owner.selectedGenreList) + owner.resetSelectedInfoData.accept(()) + owner.selectedCompletedStatus.accept(nil) + owner.selectedNovelRatingStatus.accept(nil) + } else { + // 키워드뷰 + owner.selectedKeywordList = [] + owner.selectedKeywordListData.accept(owner.selectedKeywordList) + owner.enteredText.accept("") + owner.keywordSearchResultListData.accept([]) + owner.showEmptyView.accept(false) + owner.showCategoryListView.accept(true) + } }) .disposed(by: disposeBag) @@ -372,7 +374,7 @@ final class DetailSearchViewModel: ViewModelType { .map { $0.count > 0 } .asObservable() - return Output(dismissModalViewController: dismissModalViewController.asObservable(), + return Output(popViewController: dismissModalViewController.asObservable(), selectedTab: selectedTab.asDriver(), showInfoNewImageView: showInfoNewImageView, showKeywordNewImageView: showKeywordNewImageView.asObservable(), From a8d62951e8cc432eb2bb719a52c28a438d47d040 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 19:06:08 +0900 Subject: [PATCH 17/37] =?UTF-8?q?[Chore]=20#695=20-=20=EC=97=B0=EC=9E=AC?= =?UTF-8?q?=EC=83=81=ED=83=9C=20Enum=20case=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS/Source/Data/Base/CompletedStatus.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WSSiOS/Source/Data/Base/CompletedStatus.swift b/WSSiOS/Source/Data/Base/CompletedStatus.swift index 49d0ac063..0185a2c49 100644 --- a/WSSiOS/Source/Data/Base/CompletedStatus.swift +++ b/WSSiOS/Source/Data/Base/CompletedStatus.swift @@ -8,24 +8,24 @@ import Foundation enum CompletedStatus: String, CaseIterable { + case onGoing case completed - case notCompleted var description: String { switch self { + case .onGoing: return "연재중" case .completed: return "완결작" - case .notCompleted: return "연재중" } } var isCompleted: Bool { switch self { + case .onGoing: return false case .completed: return true - case .notCompleted: return false } } init(isCompleted: Bool) { - self = isCompleted ? .completed : .notCompleted + self = isCompleted ? .completed : .onGoing } } From a3dedb8f715ad00dd25d4709fb95b4b05104b4da Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 19:34:45 +0900 Subject: [PATCH 18/37] =?UTF-8?q?[Chore]=20#695=20-=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=83=90=EC=83=89=20=EB=82=B4=20=EC=86=8C=EC=84=A4=20=EC=9E=A5?= =?UTF-8?q?=EB=A5=B4=20=EC=88=9C=EC=84=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS/Source/Data/Base/NovelGenre.swift | 1 + .../DetailSearchInfoView.swift | 2 +- .../DetailSearchViewController.swift | 6 +++--- .../DetailSearchResultViewModel.swift | 8 ++++---- .../DetailSearchViewModel.swift | 16 ++++++++-------- .../SearchViewController.swift | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/WSSiOS/Source/Data/Base/NovelGenre.swift b/WSSiOS/Source/Data/Base/NovelGenre.swift index bc0da3893..13d6a4047 100644 --- a/WSSiOS/Source/Data/Base/NovelGenre.swift +++ b/WSSiOS/Source/Data/Base/NovelGenre.swift @@ -241,4 +241,5 @@ extension NewNovelGenre { static let feedMaleGenres: [NewNovelGenre] = [.all, .fantasy, .modernFantasy, .wuxia, .drama, .mystery, .lightNovel, .romance, .romanceFantasy, .bl, .etc] static let feedFemaleGenres: [NewNovelGenre] = [.all, .romance, .romanceFantasy, .bl, .fantasy, .modernFantasy, .wuxia, .drama, .mystery, .lightNovel, .etc] static let feedFilterGenres: [NewNovelGenre] = [.fantasy, .modernFantasy, .romance, .romanceFantasy, .wuxia, .mystery, .drama, .lightNovel, .bl, .etc] + static let detailSearchGenres: [NewNovelGenre] = [.fantasy, .modernFantasy, .romance, .romanceFantasy, .wuxia, .mystery, .drama, .lightNovel, .bl] } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift index 2c6162805..e7acab2ce 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift @@ -122,7 +122,7 @@ final class DetailSearchInfoView: UIView { genreCollectionView.snp.makeConstraints { $0.top.equalTo(genreTitleLabel.snp.bottom).offset(16) $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(84) + $0.height.equalTo(88) } statusTitleLabel.snp.makeConstraints { diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index 874864f6b..803709537 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -165,7 +165,7 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { self.rootView.detailSearchInfoView.genreCollectionView.deselectItem(at: indexPath, animated: false) } - cell.bindData(genre: element.toKorean) + cell.bindData(genre: element.withKorean) } .disposed(by: disposeBag) @@ -329,7 +329,7 @@ extension DetailSearchViewController: UICollectionViewDelegateFlowLayout { if collectionView == rootView.detailSearchInfoView.genreCollectionView { var text: String? - let novelGenreList = NovelGenre.allCases.map { $0.toKorean } + let novelGenreList = NewNovelGenre.detailSearchGenres.map { $0.withKorean } text = novelGenreList[indexPath.item] guard let unwrappedText = text else { @@ -337,7 +337,7 @@ extension DetailSearchViewController: UICollectionViewDelegateFlowLayout { } let width = (unwrappedText as NSString).size(withAttributes: [NSAttributedString.Key.font: UIFont.Body2]).width + 26 - return CGSize(width: width, height: 35) + return CGSize(width: width, height: 37) } else if collectionView == rootView.detailSearchKeywordView.novelSelectedKeywordListView.selectedKeywordCollectionView{ var text: String? diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift index 7e10eac5f..cb3d35a1f 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift @@ -18,7 +18,7 @@ final class DetailSearchResultViewModel: ViewModelType { // API 쿼리 var keywords: [KeywordData] - var genres: [NovelGenre] + var genres: [NewNovelGenre] var isCompleted: Bool? var novelRating: Float? @@ -64,7 +64,7 @@ final class DetailSearchResultViewModel: ViewModelType { init(searchRepository: SearchRepository, keywords: [KeywordData], - genres: [NovelGenre], + genres: [NewNovelGenre], isCompleted: Bool?, novelRating: Float?) { self.searchRepository = searchRepository @@ -175,7 +175,7 @@ final class DetailSearchResultViewModel: ViewModelType { if let userInfo = notification.userInfo { let keywords = userInfo["keywords"] as? [KeywordData] - let genres = userInfo["genres"] as? [NovelGenre] + let genres = userInfo["genres"] as? [NewNovelGenre] let isCompleted = userInfo["isCompleted"] as? Bool let novelRating = userInfo["novelRating"] as? Float @@ -233,7 +233,7 @@ final class DetailSearchResultViewModel: ViewModelType { struct SearchFilterQuery { let keywords: [KeywordData] - let genres: [NovelGenre] + let genres: [NewNovelGenre] let isCompleted: Bool? let novelRating: Float? } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index afd653b96..ef5376056 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -27,9 +27,9 @@ final class DetailSearchViewModel: ViewModelType { private let pushToUpdateDetailSearchResultViewControllerNotificationName = Notification.Name("PushToUpdateDetailSearchResult") // 정보 - private var selectedGenreList: [NovelGenre] = [] - let selectedGenreListData = BehaviorRelay<[NovelGenre]>(value: []) - private let genreListData = PublishRelay<[NovelGenre]>() + private var selectedGenreList: [NewNovelGenre] = [] + let selectedGenreListData = BehaviorRelay<[NewNovelGenre]>(value: []) + private let genreListData = PublishRelay<[NewNovelGenre]>() private var selectedCompletedStatus = BehaviorRelay(value: nil) private var selectedNovelRatingStatus = BehaviorRelay(value: nil) private let resetSelectedInfoData = PublishRelay() @@ -89,7 +89,7 @@ final class DetailSearchViewModel: ViewModelType { let showKeywordNewImageView: Observable // 정보 - let genreListData: Observable<[NovelGenre]> + let genreListData: Observable<[NewNovelGenre]> let selectedCompletedStatus: Driver let selectedNovelRatingStatus: Driver let resetSelectedInfoData: Observable @@ -122,7 +122,7 @@ final class DetailSearchViewModel: ViewModelType { // 전체 input.viewDidLoadEvent .subscribe(with: self, onNext: { owner, _ in - owner.genreListData.accept(NovelGenre.allCases) + owner.genreListData.accept(NewNovelGenre.detailSearchGenres) owner.selectedGenreListData.accept(owner.selectedFilteredQuery.genres) owner.selectedKeywordListData.accept(owner.selectedFilteredQuery.keywords) owner.selectedCompletedStatus.accept(owner.selectedFilteredQuery.isCompleted.map { CompletedStatus(isCompleted: $0) }) @@ -187,7 +187,7 @@ final class DetailSearchViewModel: ViewModelType { .debounce(.milliseconds(300), scheduler: MainScheduler.instance) .subscribe(with: self, onNext: { owner, _ in let keywords = owner.selectedKeywordList - let genres: [NovelGenre] = owner.selectedGenreListData.value + let genres: [NewNovelGenre] = owner.selectedGenreListData.value let isCompleted = owner.selectedCompletedStatus.value?.isCompleted let novelRating = owner.selectedNovelRatingStatus.value?.toFloat @@ -215,7 +215,7 @@ final class DetailSearchViewModel: ViewModelType { input.genreColletionViewItemSelected .subscribe(with: self, onNext: { owner, indexPath in owner.selectedGenreList = owner.selectedGenreListData.value - owner.selectedGenreList.append(NovelGenre.allCases[indexPath.row]) + owner.selectedGenreList.append(NewNovelGenre.allCases[indexPath.row]) owner.selectedGenreListData.accept(owner.selectedGenreList) }) .disposed(by: disposeBag) @@ -223,7 +223,7 @@ final class DetailSearchViewModel: ViewModelType { input.genreColletionViewItemDeselected .subscribe(with: self, onNext: { owner, indexPath in owner.selectedGenreList = owner.selectedGenreListData.value - owner.selectedGenreList.removeAll { $0 == NovelGenre.allCases[indexPath.row] } + owner.selectedGenreList.removeAll { $0 == NewNovelGenre.allCases[indexPath.row] } owner.selectedGenreListData.accept(owner.selectedGenreList) }) .disposed(by: disposeBag) diff --git a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift index 41bdd3d55..61fe1dee2 100644 --- a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift @@ -95,7 +95,7 @@ final class SearchViewController: UIViewController { .subscribe(with: self, onNext: { owner, notification in if let userInfo = notification.userInfo { let keywords = userInfo["keywords"] as? [KeywordData] - let genres = userInfo["genres"] as? [NovelGenre] + let genres = userInfo["genres"] as? [NewNovelGenre] let isCompleted = userInfo["isCompleted"] as? Bool let novelRating = userInfo["novelRating"] as? Float From f2231c4d4f0e5ab32ed31f589c36568149f6ed03 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 19:47:00 +0900 Subject: [PATCH 19/37] =?UTF-8?q?[Refactor]=20#695=20-=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20NovelGenre=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20NewNo?= =?UTF-8?q?velGenre=20=ED=86=B5=ED=95=A9=20=ED=9B=84=20NovelGenre=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS/Source/Data/Base/NovelGenre.swift | 42 ++++--------------- .../Source/Data/Entity/Feed/FeedEntity.swift | 2 +- .../Source/Data/Entity/Feed/FeedFilter.swift | 2 +- .../Entity/Feed/TotalFeedListEntity.swift | 2 +- .../Data/Entity/UserFeedListEntity.swift | 2 +- .../Repository/OnboardingRepository.swift | 6 +-- ...erGenrePreferencesOtherTableViewCell.swift | 2 +- .../UserGenrePreferencesTopView.swift | 2 +- .../FeedFilterViewController.swift | 8 ++-- .../FeedCategoryCollectionViewCell.swift | 2 +- .../NovelDetailViewModel.swift | 4 +- .../OnboardingGenreButtonView.swift | 6 +-- .../OnboardingGenrePreferenceView.swift | 4 +- .../OnboardingViewModel.swift | 6 +-- .../DetailSearchViewController.swift | 2 +- .../DetailSearchResultViewModel.swift | 8 ++-- .../DetailSearchViewModel.swift | 16 +++---- .../SearchViewController.swift | 2 +- .../MyPageView/MyPageEditProfileView.swift | 9 ++-- .../MyPageEditProfileViewController.swift | 5 +-- .../MyPageEditProfileViewModel.swift | 6 +-- 21 files changed, 56 insertions(+), 82 deletions(-) diff --git a/WSSiOS/Source/Data/Base/NovelGenre.swift b/WSSiOS/Source/Data/Base/NovelGenre.swift index 13d6a4047..20c8ba0e1 100644 --- a/WSSiOS/Source/Data/Base/NovelGenre.swift +++ b/WSSiOS/Source/Data/Base/NovelGenre.swift @@ -8,33 +8,6 @@ import UIKit enum NovelGenre: String, CaseIterable { - case romance, romanceFantasy, fantasy, modernFantasy, wuxia, BL, lightNovel, mystery, drama - - var toKorean: String { - switch self { - case .romanceFantasy: - return "로판" - case .romance: - return "로맨스" - case .fantasy: - return "판타지" - case .modernFantasy: - return "현판" - case .drama: - return "드라마" - case .lightNovel: - return "라노벨" - case .wuxia: - return "무협" - case .mystery: - return "미스터리" - case .BL: - return "BL" - } - } -} - -enum NewNovelGenre: String, CaseIterable { case all = "all" case fantasy = "fantasy" case modernFantasy = "modernFantasy" @@ -77,7 +50,7 @@ enum NewNovelGenre: String, CaseIterable { } } - static func withKoreanRawValue(from genre: String) -> NewNovelGenre { + static func withKoreanRawValue(from genre: String) -> NovelGenre { switch genre { case "전체": return .all @@ -236,10 +209,11 @@ enum NewNovelGenre: String, CaseIterable { } } -extension NewNovelGenre { - static let onboardingGenres: [NewNovelGenre] = [.romance, .romanceFantasy, .bl, .fantasy, .modernFantasy, .wuxia, .lightNovel, .drama, .mystery] - static let feedMaleGenres: [NewNovelGenre] = [.all, .fantasy, .modernFantasy, .wuxia, .drama, .mystery, .lightNovel, .romance, .romanceFantasy, .bl, .etc] - static let feedFemaleGenres: [NewNovelGenre] = [.all, .romance, .romanceFantasy, .bl, .fantasy, .modernFantasy, .wuxia, .drama, .mystery, .lightNovel, .etc] - static let feedFilterGenres: [NewNovelGenre] = [.fantasy, .modernFantasy, .romance, .romanceFantasy, .wuxia, .mystery, .drama, .lightNovel, .bl, .etc] - static let detailSearchGenres: [NewNovelGenre] = [.fantasy, .modernFantasy, .romance, .romanceFantasy, .wuxia, .mystery, .drama, .lightNovel, .bl] +extension NovelGenre { + static let onboardingGenres: [NovelGenre] = [.romance, .romanceFantasy, .bl, .fantasy, .modernFantasy, .wuxia, .lightNovel, .drama, .mystery] + static let feedMaleGenres: [NovelGenre] = [.all, .fantasy, .modernFantasy, .wuxia, .drama, .mystery, .lightNovel, .romance, .romanceFantasy, .bl, .etc] + static let feedFemaleGenres: [NovelGenre] = [.all, .romance, .romanceFantasy, .bl, .fantasy, .modernFantasy, .wuxia, .drama, .mystery, .lightNovel, .etc] + static let feedFilterGenres: [NovelGenre] = [.fantasy, .modernFantasy, .romance, .romanceFantasy, .wuxia, .mystery, .drama, .lightNovel, .bl, .etc] + static let detailSearchGenres: [NovelGenre] = [.fantasy, .modernFantasy, .romance, .romanceFantasy, .wuxia, .mystery, .drama, .lightNovel, .bl] + static let myPageEditGenres: [NovelGenre] = [.romance, .romanceFantasy, .fantasy, .modernFantasy, .wuxia, .bl, .lightNovel, .mystery, .drama] } diff --git a/WSSiOS/Source/Data/Entity/Feed/FeedEntity.swift b/WSSiOS/Source/Data/Entity/Feed/FeedEntity.swift index 87d10db69..a1a54da38 100644 --- a/WSSiOS/Source/Data/Entity/Feed/FeedEntity.swift +++ b/WSSiOS/Source/Data/Entity/Feed/FeedEntity.swift @@ -74,7 +74,7 @@ extension FeedResponse { let title = self.title, let rating = self.novelRating, let genreRaw = self.novelGenre, - let genre = NewNovelGenre(rawValue: genreRaw), + let genre = NovelGenre(rawValue: genreRaw), let description = self.novelDescription, let thumbnailPath = self.novelThumbnailImage, let thumbnailURL = KingFisherRxHelper.makeImageURLString(path: thumbnailPath) diff --git a/WSSiOS/Source/Data/Entity/Feed/FeedFilter.swift b/WSSiOS/Source/Data/Entity/Feed/FeedFilter.swift index b09dbbaea..2ac1ffbc1 100644 --- a/WSSiOS/Source/Data/Entity/Feed/FeedFilter.swift +++ b/WSSiOS/Source/Data/Entity/Feed/FeedFilter.swift @@ -33,6 +33,6 @@ enum FeedVisibilityOption: CaseIterable { } struct FeedFilterOption: Equatable { - var genres: [NewNovelGenre] = NewNovelGenre.feedFilterGenres + var genres: [NovelGenre] = NovelGenre.feedFilterGenres var visibilityOptions: [FeedVisibilityOption] = [.public, .private] } diff --git a/WSSiOS/Source/Data/Entity/Feed/TotalFeedListEntity.swift b/WSSiOS/Source/Data/Entity/Feed/TotalFeedListEntity.swift index 77b6cd0cc..6c6abb74f 100644 --- a/WSSiOS/Source/Data/Entity/Feed/TotalFeedListEntity.swift +++ b/WSSiOS/Source/Data/Entity/Feed/TotalFeedListEntity.swift @@ -91,7 +91,7 @@ extension TotalFeedResponse { let thumbnailImageURL = URL(string: self.thumbnailUrl ?? "") let hasImage = self.thumbnailUrl != nil && self.imageCount > 0 - let genre = NewNovelGenre(rawValue: self.genreName ?? "") + let genre = NovelGenre(rawValue: self.genreName ?? "") let novelGenreColor = genre?.linkColor ?? .genreColorR let novelGenreImage = genre?.linkImage ?? .icGenreLinkR diff --git a/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift b/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift index ce52b6306..3a05f34d4 100644 --- a/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift +++ b/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift @@ -55,7 +55,7 @@ extension UserFeedResponse { } let hasImage = self.thumbnailUrl != nil && self.imageCount > 0 - let genre = NewNovelGenre(rawValue: self.genre ?? "") + let genre = NovelGenre(rawValue: self.genre ?? "") let novelGenreColor = genre?.linkColor ?? .genreColorR let novelGenreImage = genre?.linkImage ?? .icGenreLinkR let thumbnailImageURL = KingFisherRxHelper.makeImageURLString(path: self.thumbnailUrl ?? "") diff --git a/WSSiOS/Source/Data/Repository/OnboardingRepository.swift b/WSSiOS/Source/Data/Repository/OnboardingRepository.swift index 7be0a0f94..086eb7187 100644 --- a/WSSiOS/Source/Data/Repository/OnboardingRepository.swift +++ b/WSSiOS/Source/Data/Repository/OnboardingRepository.swift @@ -11,7 +11,7 @@ import RxSwift protocol OnboardingRepository { func getNicknameisValid(_ nickname: String) -> Single - func postUserProfile(nickname: String, gender: OnboardingGender, birth: Int, genrePreferences: [NewNovelGenre]) -> Single + func postUserProfile(nickname: String, gender: OnboardingGender, birth: Int, genrePreferences: [NovelGenre]) -> Single } struct TestOnboardingRepository: OnboardingRepository { @@ -19,7 +19,7 @@ struct TestOnboardingRepository: OnboardingRepository { return Single.just(OnboardingResponse(isValid: false)) } - func postUserProfile(nickname: String, gender: OnboardingGender, birth: Int, genrePreferences: [NewNovelGenre]) -> Single { + func postUserProfile(nickname: String, gender: OnboardingGender, birth: Int, genrePreferences: [NovelGenre]) -> Single { return Single.just(()) } } @@ -35,7 +35,7 @@ struct DefaultOnboardingRepository: OnboardingRepository { return onboardingService.getNicknameisValid(nickname) } - func postUserProfile(nickname: String, gender: OnboardingGender, birth: Int, genrePreferences: [NewNovelGenre]) -> Single { + func postUserProfile(nickname: String, gender: OnboardingGender, birth: Int, genrePreferences: [NovelGenre]) -> Single { let userInfoResult = UserInfoRequest( nickname: nickname, gender: gender.rawValue, diff --git a/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesOtherTableViewCell.swift b/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesOtherTableViewCell.swift index cceb09359..e89496c32 100644 --- a/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesOtherTableViewCell.swift +++ b/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesOtherTableViewCell.swift @@ -68,7 +68,7 @@ final class UserGenrePreferencesOtherTableViewCell: UITableViewCell { func bindData(data: UserGenrePreferencesEntity) { genreImageView.kfSetImage(url: data.genreImageURL) - let koreanGenre = NewNovelGenre(rawValue: data.genreName)?.withKorean + let koreanGenre = NovelGenre(rawValue: data.genreName)?.withKorean genreLabel.applyWSSFont(.title3, with: koreanGenre) countLabel.applyWSSFont(.body5, with: String(data.genreCount) + "편") } diff --git a/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesTopView.swift b/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesTopView.swift index 3f0839036..47cc80175 100644 --- a/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesTopView.swift +++ b/WSSiOS/Source/Presentation/Base/UserPreferences/UserGenrePreferencesTopView.swift @@ -70,7 +70,7 @@ final class UserGenrePreferencesTopView: UIView { func bindData(data: UserGenrePreferencesEntity) { topGenreImageView.kfSetImage(url: data.genreImageURL) - let koreanGenre = NewNovelGenre(rawValue: data.genreName)?.withKorean + let koreanGenre = NovelGenre(rawValue: data.genreName)?.withKorean topGenreTitleLabel.applyWSSFont(.title3, with: koreanGenre) topGenreCountLabel.applyWSSFont(.body5, with: String(data.genreCount) + "편") } diff --git a/WSSiOS/Source/Presentation/Feed/Filter/FeedFilterViewController/FeedFilterViewController.swift b/WSSiOS/Source/Presentation/Feed/Filter/FeedFilterViewController/FeedFilterViewController.swift index 92b954a28..157d58655 100644 --- a/WSSiOS/Source/Presentation/Feed/Filter/FeedFilterViewController/FeedFilterViewController.swift +++ b/WSSiOS/Source/Presentation/Feed/Filter/FeedFilterViewController/FeedFilterViewController.swift @@ -20,7 +20,7 @@ final class FeedFilterViewController: UIViewController { private let disposeBag = DisposeBag() private let initialFilterOption: FeedFilterOption let filterOption = PublishSubject() - private let genreOptions = BehaviorRelay<[NewNovelGenre]>(value: NewNovelGenre.feedFilterGenres) + private let genreOptions = BehaviorRelay<[NovelGenre]>(value: NovelGenre.feedFilterGenres) private let visibilityOptions = BehaviorRelay<[FeedVisibilityOption]>(value: FeedVisibilityOption.allCases) //MARK: - Components @@ -110,7 +110,7 @@ final class FeedFilterViewController: UIViewController { .subscribe(with: self, onNext: { owner, _ in let selectedIndexPaths = owner.rootView.genreView.genreCollectionView.indexPathsForSelectedItems ?? [] let selectedGenres = selectedIndexPaths.map { indexPath in - NewNovelGenre.feedFilterGenres[indexPath.row] + NovelGenre.feedFilterGenres[indexPath.row] } owner.genreOptions.accept(selectedGenres) @@ -119,7 +119,7 @@ final class FeedFilterViewController: UIViewController { } private func bindOutput() { - Observable<[NewNovelGenre]>.just(NewNovelGenre.feedFilterGenres) + Observable<[NovelGenre]>.just(NovelGenre.feedFilterGenres) .bind(to: rootView.genreView.genreCollectionView.rx.items( cellIdentifier: FeedFilterGenreCollectionViewCell.cellIdentifier, cellType: FeedFilterGenreCollectionViewCell .self)) { item, element, cell in @@ -178,7 +178,7 @@ extension FeedFilterViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { var text: String? - let novelGenreList = NewNovelGenre.feedFilterGenres.map { $0.withKorean } + let novelGenreList = NovelGenre.feedFilterGenres.map { $0.withKorean } text = novelGenreList[indexPath.item] guard let unwrappedText = text else { diff --git a/WSSiOS/Source/Presentation/FeedEdit/FeedEditViewCell/FeedCategoryCollectionViewCell.swift b/WSSiOS/Source/Presentation/FeedEdit/FeedEditViewCell/FeedCategoryCollectionViewCell.swift index a83b8f9fd..23893d7c4 100644 --- a/WSSiOS/Source/Presentation/FeedEdit/FeedEditViewCell/FeedCategoryCollectionViewCell.swift +++ b/WSSiOS/Source/Presentation/FeedEdit/FeedEditViewCell/FeedCategoryCollectionViewCell.swift @@ -52,7 +52,7 @@ final class FeedCategoryCollectionViewCell: UICollectionViewCell { //MARK: - Data - func bindData(category: NewNovelGenre) { + func bindData(category: NovelGenre) { self.keywordLink.setText(category.withKorean) } } diff --git a/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewModel/NovelDetailViewModel.swift b/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewModel/NovelDetailViewModel.swift index 5f4d0b1d3..fb02f7b14 100644 --- a/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewModel/NovelDetailViewModel.swift +++ b/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewModel/NovelDetailViewModel.swift @@ -36,7 +36,7 @@ final class NovelDetailViewModel: ViewModelType { private let showLargeNovelCoverImage = BehaviorRelay(value: false) private let isUserNovelInterested = BehaviorRelay(value: false) private let readStatus = BehaviorRelay(value: nil) - private let novelGenre = BehaviorRelay<[NewNovelGenre]>(value: []) + private let novelGenre = BehaviorRelay<[NovelGenre]>(value: []) private let pushToAuthorSearchResultViewController = PublishRelay() // Tab @@ -570,7 +570,7 @@ final class NovelDetailViewModel: ViewModelType { owner.novelGenre.accept(data.novelGenre.split{ $0 == "/"} .map{ String($0) } - .map { NewNovelGenre.withKoreanRawValue(from: $0) }) + .map { NovelGenre.withKoreanRawValue(from: $0) }) }, onFailure: { owner, error in owner.showNetworkErrorView.accept(true) print("Error: \(error)") diff --git a/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenreButtonView.swift b/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenreButtonView.swift index 0d5633959..8bbcf599e 100644 --- a/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenreButtonView.swift +++ b/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenreButtonView.swift @@ -14,7 +14,7 @@ final class OnboardingGenreButtonView: UIView { //MARK: - Properties - let genre: NewNovelGenre + let genre: NovelGenre private let buttonPaddingSum: CGFloat = 126 private var buttonSize: CGFloat { @@ -34,7 +34,7 @@ final class OnboardingGenreButtonView: UIView { //MARK: - Life Cycle - init(genre: NewNovelGenre) { + init(genre: NovelGenre) { self.genre = genre super.init(frame: .zero) @@ -108,7 +108,7 @@ final class OnboardingGenreButtonView: UIView { // MARK: - Custom Method - func updateButton(selectedGenres: [NewNovelGenre]) { + func updateButton(selectedGenres: [NovelGenre]) { let isSelected = selectedGenres.contains(genre) genreButton.do { diff --git a/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenrePreferenceView.swift b/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenrePreferenceView.swift index 824aec94a..1f30d5359 100644 --- a/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenrePreferenceView.swift +++ b/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingView/OnboardingAssistantView/OnboardingGenrePreferenceView/OnboardingGenrePreferenceView.swift @@ -17,7 +17,7 @@ final class OnboardingGenrePreferenceView: UIView { private let titleLabel = UILabel() private let descriptionLabel = UILabel() - let genreButtons: [OnboardingGenreButtonView] = NewNovelGenre.onboardingGenres + let genreButtons: [OnboardingGenreButtonView] = NovelGenre.onboardingGenres .map { OnboardingGenreButtonView(genre: $0) } let totalGenreStackView = UIStackView() @@ -113,7 +113,7 @@ final class OnboardingGenrePreferenceView: UIView { // MARK: - Custom Method - func updateGenreButtons(selectedGenres: [NewNovelGenre]) { + func updateGenreButtons(selectedGenres: [NovelGenre]) { genreButtons.forEach { $0.updateButton(selectedGenres: selectedGenres) } diff --git a/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingViewModel/OnboardingViewModel.swift b/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingViewModel/OnboardingViewModel.swift index c19450ad3..344b495ff 100644 --- a/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingViewModel/OnboardingViewModel.swift +++ b/WSSiOS/Source/Presentation/Onboarding/Onboarding/OnboardingViewModel/OnboardingViewModel.swift @@ -34,7 +34,7 @@ final class OnboardingViewModel: ViewModelType { private let isBirthGenderNextButtonAvailable = BehaviorRelay(value: false) // GenrePreference - private let selectedGenres = BehaviorRelay<[NewNovelGenre]>(value: []) + private let selectedGenres = BehaviorRelay<[NovelGenre]>(value: []) private let isGenrePreferenceNextButtonAvailable = BehaviorRelay(value: false) // Total @@ -71,7 +71,7 @@ final class OnboardingViewModel: ViewModelType { let selectedBirth: Observable // GenrePreference - let genreButtonDidTap: Observable + let genreButtonDidTap: Observable // Total let viewDidLoadEvent: Observable @@ -97,7 +97,7 @@ final class OnboardingViewModel: ViewModelType { let isBirthGenderNextButtonEnabled: Driver // GenrePrefernece - let selectedGenres: Driver<[NewNovelGenre]> + let selectedGenres: Driver<[NovelGenre]> let isGenrePreferenceNextButtonEnabled: Driver // Total diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index 803709537..f9fd9d4b9 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -329,7 +329,7 @@ extension DetailSearchViewController: UICollectionViewDelegateFlowLayout { if collectionView == rootView.detailSearchInfoView.genreCollectionView { var text: String? - let novelGenreList = NewNovelGenre.detailSearchGenres.map { $0.withKorean } + let novelGenreList = NovelGenre.detailSearchGenres.map { $0.withKorean } text = novelGenreList[indexPath.item] guard let unwrappedText = text else { diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift index cb3d35a1f..7e10eac5f 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift @@ -18,7 +18,7 @@ final class DetailSearchResultViewModel: ViewModelType { // API 쿼리 var keywords: [KeywordData] - var genres: [NewNovelGenre] + var genres: [NovelGenre] var isCompleted: Bool? var novelRating: Float? @@ -64,7 +64,7 @@ final class DetailSearchResultViewModel: ViewModelType { init(searchRepository: SearchRepository, keywords: [KeywordData], - genres: [NewNovelGenre], + genres: [NovelGenre], isCompleted: Bool?, novelRating: Float?) { self.searchRepository = searchRepository @@ -175,7 +175,7 @@ final class DetailSearchResultViewModel: ViewModelType { if let userInfo = notification.userInfo { let keywords = userInfo["keywords"] as? [KeywordData] - let genres = userInfo["genres"] as? [NewNovelGenre] + let genres = userInfo["genres"] as? [NovelGenre] let isCompleted = userInfo["isCompleted"] as? Bool let novelRating = userInfo["novelRating"] as? Float @@ -233,7 +233,7 @@ final class DetailSearchResultViewModel: ViewModelType { struct SearchFilterQuery { let keywords: [KeywordData] - let genres: [NewNovelGenre] + let genres: [NovelGenre] let isCompleted: Bool? let novelRating: Float? } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index ef5376056..30d2d6744 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -27,9 +27,9 @@ final class DetailSearchViewModel: ViewModelType { private let pushToUpdateDetailSearchResultViewControllerNotificationName = Notification.Name("PushToUpdateDetailSearchResult") // 정보 - private var selectedGenreList: [NewNovelGenre] = [] - let selectedGenreListData = BehaviorRelay<[NewNovelGenre]>(value: []) - private let genreListData = PublishRelay<[NewNovelGenre]>() + private var selectedGenreList: [NovelGenre] = [] + let selectedGenreListData = BehaviorRelay<[NovelGenre]>(value: []) + private let genreListData = PublishRelay<[NovelGenre]>() private var selectedCompletedStatus = BehaviorRelay(value: nil) private var selectedNovelRatingStatus = BehaviorRelay(value: nil) private let resetSelectedInfoData = PublishRelay() @@ -89,7 +89,7 @@ final class DetailSearchViewModel: ViewModelType { let showKeywordNewImageView: Observable // 정보 - let genreListData: Observable<[NewNovelGenre]> + let genreListData: Observable<[NovelGenre]> let selectedCompletedStatus: Driver let selectedNovelRatingStatus: Driver let resetSelectedInfoData: Observable @@ -122,7 +122,7 @@ final class DetailSearchViewModel: ViewModelType { // 전체 input.viewDidLoadEvent .subscribe(with: self, onNext: { owner, _ in - owner.genreListData.accept(NewNovelGenre.detailSearchGenres) + owner.genreListData.accept(NovelGenre.detailSearchGenres) owner.selectedGenreListData.accept(owner.selectedFilteredQuery.genres) owner.selectedKeywordListData.accept(owner.selectedFilteredQuery.keywords) owner.selectedCompletedStatus.accept(owner.selectedFilteredQuery.isCompleted.map { CompletedStatus(isCompleted: $0) }) @@ -187,7 +187,7 @@ final class DetailSearchViewModel: ViewModelType { .debounce(.milliseconds(300), scheduler: MainScheduler.instance) .subscribe(with: self, onNext: { owner, _ in let keywords = owner.selectedKeywordList - let genres: [NewNovelGenre] = owner.selectedGenreListData.value + let genres: [NovelGenre] = owner.selectedGenreListData.value let isCompleted = owner.selectedCompletedStatus.value?.isCompleted let novelRating = owner.selectedNovelRatingStatus.value?.toFloat @@ -215,7 +215,7 @@ final class DetailSearchViewModel: ViewModelType { input.genreColletionViewItemSelected .subscribe(with: self, onNext: { owner, indexPath in owner.selectedGenreList = owner.selectedGenreListData.value - owner.selectedGenreList.append(NewNovelGenre.allCases[indexPath.row]) + owner.selectedGenreList.append(NovelGenre.allCases[indexPath.row]) owner.selectedGenreListData.accept(owner.selectedGenreList) }) .disposed(by: disposeBag) @@ -223,7 +223,7 @@ final class DetailSearchViewModel: ViewModelType { input.genreColletionViewItemDeselected .subscribe(with: self, onNext: { owner, indexPath in owner.selectedGenreList = owner.selectedGenreListData.value - owner.selectedGenreList.removeAll { $0 == NewNovelGenre.allCases[indexPath.row] } + owner.selectedGenreList.removeAll { $0 == NovelGenre.allCases[indexPath.row] } owner.selectedGenreListData.accept(owner.selectedGenreList) }) .disposed(by: disposeBag) diff --git a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift index 61fe1dee2..41bdd3d55 100644 --- a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift @@ -95,7 +95,7 @@ final class SearchViewController: UIViewController { .subscribe(with: self, onNext: { owner, notification in if let userInfo = notification.userInfo { let keywords = userInfo["keywords"] as? [KeywordData] - let genres = userInfo["genres"] as? [NewNovelGenre] + let genres = userInfo["genres"] as? [NovelGenre] let isCompleted = userInfo["isCompleted"] as? Bool let novelRating = userInfo["novelRating"] as? Float diff --git a/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageView/MyPageEditProfileView.swift b/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageView/MyPageEditProfileView.swift index 9ebc03dea..15f02bdd6 100644 --- a/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageView/MyPageEditProfileView.swift +++ b/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageView/MyPageEditProfileView.swift @@ -334,7 +334,8 @@ final class MyPageEditProfileView: UIView { genreCollectionView.snp.makeConstraints { $0.top.equalTo(genreDescriptionLabel.snp.bottom).offset(14) - $0.leading.trailing.equalToSuperview().inset(20) + $0.leading.equalToSuperview().inset(20) + $0.trailing.equalToSuperview().inset(30) $0.bottom.equalToSuperview() } } @@ -348,11 +349,11 @@ final class MyPageEditProfileView: UIView { completeButton.snp.makeConstraints { $0.width.equalTo(48) - $0.height.equalTo(42) + $0.height.equalTo(42).priority(.high) } - + backButton.snp.makeConstraints { - $0.size.equalTo(44) + $0.size.equalTo(44).priority(.high) } } } diff --git a/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewController/MyPageEditProfileViewController.swift b/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewController/MyPageEditProfileViewController.swift index a6b4fcf3b..b5c73dfea 100644 --- a/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewController/MyPageEditProfileViewController.swift +++ b/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewController/MyPageEditProfileViewController.swift @@ -103,7 +103,6 @@ final class MyPageEditProfileViewController: UIViewController { .bind(to: rootView.genreCollectionView.rx.items( cellIdentifier: MyPageEditProfileGenreCollectionViewCell.cellIdentifier, cellType: MyPageEditProfileGenreCollectionViewCell.self)) { row, element, cell in - print(element) cell.bindData(genre: element.0) cell.updateCell(isSelected: element.1) } @@ -246,7 +245,7 @@ extension MyPageEditProfileViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { var text: String? - let genreList: [String] = NovelGenre.allCases.map { $0.toKorean } + let genreList: [String] = NovelGenre.myPageEditGenres.map { $0.withKorean } text = genreList[indexPath.item] guard let unwrappedText = text else { @@ -254,6 +253,6 @@ extension MyPageEditProfileViewController: UICollectionViewDelegateFlowLayout { } let width = (unwrappedText as NSString).size(withAttributes: [NSAttributedString.Key.font: UIFont.Body2]).width + 26 - return CGSize(width: width, height: 35) + return CGSize(width: width, height: 37) } } diff --git a/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewModel/MyPageEditProfileViewModel.swift b/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewModel/MyPageEditProfileViewModel.swift index 8202271c0..8e34f913d 100644 --- a/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewModel/MyPageEditProfileViewModel.swift +++ b/WSSiOS/Source/Presentation/UserPage/MyPage/MyPageViewModel/MyPageEditProfileViewModel.swift @@ -26,7 +26,7 @@ final class MyPageEditProfileViewModel: ViewModelType { private var avatarId: Int = -1 // 고정값 - private let genreList: [String] = NovelGenre.allCases.map { $0.toKorean } + private let genreList: [String] = NovelGenre.myPageEditGenres.map { $0.withKorean } private let nicknamePattern = "^[a-zA-Z0-9가-힣]{2,10}$" static let nicknameLimit = 10 static let introLimit = 50 @@ -293,7 +293,7 @@ final class MyPageEditProfileViewModel: ViewModelType { input.genreCellTap .bind(with: self, onNext: { owner, indexPath in let cellContent = owner.genreList[indexPath.row] - let toEnglish = NewNovelGenre.withKoreanRawValue(from: cellContent).rawValue + let toEnglish = NovelGenre.withKoreanRawValue(from: cellContent).rawValue let update = owner.checkGenreToUpdateCell(owner.userGenre.value, toEnglish) var updatedGenres = owner.userGenre.value @@ -314,7 +314,7 @@ final class MyPageEditProfileViewModel: ViewModelType { private func checkGenreToMakeTuple(_ totalGenre: [String], _ myGenre: [String]) -> [(String, Bool)] { let toKorean = myGenre.compactMap { genre in - NewNovelGenre(rawValue: genre)?.withKorean + NovelGenre(rawValue: genre)?.withKorean } return totalGenre.map { genre in From bc86ea346b585afa89844053b1de72f59f427161 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 19:55:37 +0900 Subject: [PATCH 20/37] =?UTF-8?q?[Fix]=20#695=20-=20KeywordLink=20?= =?UTF-8?q?=EB=86=92=EC=9D=B4=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B6=A9=EB=8F=8C=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NovelKeywordSelectSearchResultCollectionViewCell.swift | 1 - .../DetailSearchInfoGenreCollectionViewCell.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelKeywordSelectSearchResultCollectionViewCell.swift b/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelKeywordSelectSearchResultCollectionViewCell.swift index 0b43fdf35..7f2ba113a 100644 --- a/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelKeywordSelectSearchResultCollectionViewCell.swift +++ b/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelKeywordSelectSearchResultCollectionViewCell.swift @@ -46,7 +46,6 @@ final class NovelKeywordSelectSearchResultCollectionViewCell: UICollectionViewCe private func setLayout() { keywordLink.snp.makeConstraints { $0.edges.equalToSuperview() - $0.height.equalTo(35) } } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchCell/DetailSearchInfoGenreCollectionViewCell.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchCell/DetailSearchInfoGenreCollectionViewCell.swift index de3de5daf..d37f83ed6 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchCell/DetailSearchInfoGenreCollectionViewCell.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchCell/DetailSearchInfoGenreCollectionViewCell.swift @@ -46,7 +46,6 @@ final class DetailSearchInfoGenreCollectionViewCell: UICollectionViewCell { private func setLayout() { genreKeywordView.snp.makeConstraints { $0.edges.equalToSuperview() - $0.height.equalTo(35) } } From f03f044974dbad227a3dfafcfa02b00daf4c8a2b Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 20:07:34 +0900 Subject: [PATCH 21/37] =?UTF-8?q?[Chore]=20#695=20-=20PublicationStatus=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=EB=B3=84=EC=A0=90=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...edStatus.swift => PublicationStatus.swift} | 2 +- .../DetailSearchCompletedStatusButton.swift | 6 +-- .../DetailSearchInfoView.swift | 49 ++----------------- .../DetailSearchViewController.swift | 17 +------ .../DetailSearchViewModel.swift | 28 +++-------- 5 files changed, 16 insertions(+), 86 deletions(-) rename WSSiOS/Source/Data/Base/{CompletedStatus.swift => PublicationStatus.swift} (91%) diff --git a/WSSiOS/Source/Data/Base/CompletedStatus.swift b/WSSiOS/Source/Data/Base/PublicationStatus.swift similarity index 91% rename from WSSiOS/Source/Data/Base/CompletedStatus.swift rename to WSSiOS/Source/Data/Base/PublicationStatus.swift index 0185a2c49..77f021181 100644 --- a/WSSiOS/Source/Data/Base/CompletedStatus.swift +++ b/WSSiOS/Source/Data/Base/PublicationStatus.swift @@ -7,7 +7,7 @@ import Foundation -enum CompletedStatus: String, CaseIterable { +enum PublicationStatus: String, CaseIterable { case onGoing case completed diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchCompletedStatusButton.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchCompletedStatusButton.swift index 098d1c9cd..4775c1351 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchCompletedStatusButton.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchCompletedStatusButton.swift @@ -14,7 +14,7 @@ final class DetailSearchCompletedStatusButton: UIButton { //MARK: - Properties - let status: CompletedStatus + let status: PublicationStatus //MARK: - Components @@ -22,7 +22,7 @@ final class DetailSearchCompletedStatusButton: UIButton { //MARK: - Life Cycle - init(status: CompletedStatus) { + init(status: PublicationStatus) { self.status = status super.init(frame: .zero) @@ -63,7 +63,7 @@ final class DetailSearchCompletedStatusButton: UIButton { // MARK: - Custom Method - func updateButton(selectedCompletedStatus: CompletedStatus?) { + func updateButton(selectedCompletedStatus: PublicationStatus?) { let isSelected = selectedCompletedStatus == status self.do { diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift index e7acab2ce..930e62122 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift @@ -23,16 +23,11 @@ final class DetailSearchInfoView: UIView { private let statusTitleLabel = UILabel() private let statusStackView = UIStackView() - let completedStatusButtons = CompletedStatus.allCases.map { DetailSearchCompletedStatusButton(status: $0) } + let completedStatusButtons = PublicationStatus.allCases.map { DetailSearchCompletedStatusButton(status: $0) } /// 평점 private let ratingTitleLabel = UILabel() - private let ratingTopStackView = UIStackView() - private let ratingBottomStackView = UIStackView() - - let novelRatingStatusButtons = NovelRatingStatus.allCases.map { WSSNovelRatingStatusButton(status: $0) } - //MARK: - Life Cycle override init(frame: CGRect) { @@ -81,36 +76,16 @@ final class DetailSearchInfoView: UIView { $0.applyWSSFont(.title2, with: StringLiterals.DetailSearch.rating) $0.textColor = .wssBlack } - - ratingTopStackView.do { - $0.axis = .horizontal - $0.spacing = 11 - $0.distribution = .fillEqually - } - - ratingBottomStackView.do { - $0.axis = .horizontal - $0.spacing = 11 - $0.distribution = .fillEqually - } } private func setHierarchy() { completedStatusButtons.forEach { statusStackView.addArrangedSubview($0) } - let topRowButtons = Array(novelRatingStatusButtons.prefix(2)) - let bottomRowButtons = Array(novelRatingStatusButtons.suffix(2)) - - topRowButtons.forEach { ratingTopStackView.addArrangedSubview($0) } - bottomRowButtons.forEach { ratingBottomStackView.addArrangedSubview($0) } - self.addSubviews(genreTitleLabel, genreCollectionView, statusTitleLabel, statusStackView, - ratingTitleLabel, - ratingTopStackView, - ratingBottomStackView) + ratingTitleLabel) } private func setLayout() { @@ -140,32 +115,14 @@ final class DetailSearchInfoView: UIView { $0.top.equalTo(statusStackView.snp.bottom).offset(42) $0.leading.equalToSuperview().inset(20) } - - ratingTopStackView.snp.makeConstraints { - $0.top.equalTo(ratingTitleLabel.snp.bottom).offset(16) - $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(43) - } - - ratingBottomStackView.snp.makeConstraints { - $0.top.equalTo(ratingTopStackView.snp.bottom).offset(10) - $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(43) - } } - func updateCompletedKeyword(_ selectedCompletedStatus: CompletedStatus?) { + func updateCompletedKeyword(_ selectedCompletedStatus: PublicationStatus?) { completedStatusButtons.forEach { $0.updateButton(selectedCompletedStatus: selectedCompletedStatus) } } - func updateNovelRatingKeyword(_ selectedNovelRatingStatus: NovelRatingStatus?) { - novelRatingStatusButtons.forEach { - $0.updateButton(selectedNovelRatingStatus: selectedNovelRatingStatus) - } - } - func resetAllStates() { genreCollectionView.indexPathsForSelectedItems?.forEach { indexPath in genreCollectionView.deselectItem(at: indexPath, animated: false) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index f9fd9d4b9..6110df4e1 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -94,12 +94,6 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { button.rx.tap.map { button.status } }) - let novelRatingStatusButtonDidTap = Observable.merge( - rootView.detailSearchInfoView.novelRatingStatusButtons - .map { button in - button.rx.tap.map { button.status } - }) - let input = DetailSearchViewModel.Input( viewDidLoadEvent: viewDidLoadEvent.asObservable(), closeButtonDidTap: rootView.detailSearchHeaderView.backButton.rx.tap, @@ -110,8 +104,7 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { updateDetailSearchResultData: NotificationCenter.default.rx.notification(Notification.Name("PushToUpateDetailSearchResult")).asObservable(), genreColletionViewItemSelected: rootView.detailSearchInfoView.genreCollectionView.rx.itemSelected.asObservable(), genreColletionViewItemDeselected: rootView.detailSearchInfoView.genreCollectionView.rx.itemDeselected.asObservable(), - completedButtonDidTap: completedStatusButtonDidTap, - novelRatingButtonDidTap: novelRatingStatusButtonDidTap, + publicationStatusButtonDidTap: completedStatusButtonDidTap, updatedEnteredText: rootView.detailSearchKeywordView.novelKeywordSelectSearchBarView.keywordTextField.rx.text.orEmpty.distinctUntilChanged().asObservable(), keywordTextFieldEditingDidBegin: rootView.detailSearchKeywordView.novelKeywordSelectSearchBarView.keywordTextField.rx.controlEvent(.editingDidBegin).asControlEvent(), keywordTextFieldEditingDidEnd: rootView.detailSearchKeywordView.novelKeywordSelectSearchBarView.keywordTextField.rx.controlEvent(.editingDidEnd).asControlEvent(), @@ -170,18 +163,12 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { .disposed(by: disposeBag) - output.selectedCompletedStatus + output.selectedPublicationStatus .drive(with: self, onNext: { owner, selectedCompletedStatus in owner.rootView.detailSearchInfoView.updateCompletedKeyword(selectedCompletedStatus) }) .disposed(by: disposeBag) - output.selectedNovelRatingStatus - .drive(with: self, onNext: { owner, selectedNovelRatingStatus in - owner.rootView.detailSearchInfoView.updateNovelRatingKeyword(selectedNovelRatingStatus) - }) - .disposed(by: disposeBag) - output.resetSelectedInfoData .subscribe(with: self, onNext: { owner, _ in owner.rootView.detailSearchInfoView.resetAllStates() diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index 30d2d6744..7e16269f0 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -30,7 +30,7 @@ final class DetailSearchViewModel: ViewModelType { private var selectedGenreList: [NovelGenre] = [] let selectedGenreListData = BehaviorRelay<[NovelGenre]>(value: []) private let genreListData = PublishRelay<[NovelGenre]>() - private var selectedCompletedStatus = BehaviorRelay(value: nil) + private var selectedCompletedStatus = BehaviorRelay(value: nil) private var selectedNovelRatingStatus = BehaviorRelay(value: nil) private let resetSelectedInfoData = PublishRelay() @@ -62,9 +62,7 @@ final class DetailSearchViewModel: ViewModelType { // 정보 let genreColletionViewItemSelected: Observable let genreColletionViewItemDeselected: Observable - - let completedButtonDidTap: Observable - let novelRatingButtonDidTap: Observable + let publicationStatusButtonDidTap: Observable // 키워드 let updatedEnteredText: Observable @@ -90,8 +88,7 @@ final class DetailSearchViewModel: ViewModelType { // 정보 let genreListData: Observable<[NovelGenre]> - let selectedCompletedStatus: Driver - let selectedNovelRatingStatus: Driver + let selectedPublicationStatus: Driver let resetSelectedInfoData: Observable // 키워드 @@ -125,7 +122,7 @@ final class DetailSearchViewModel: ViewModelType { owner.genreListData.accept(NovelGenre.detailSearchGenres) owner.selectedGenreListData.accept(owner.selectedFilteredQuery.genres) owner.selectedKeywordListData.accept(owner.selectedFilteredQuery.keywords) - owner.selectedCompletedStatus.accept(owner.selectedFilteredQuery.isCompleted.map { CompletedStatus(isCompleted: $0) }) + owner.selectedCompletedStatus.accept(owner.selectedFilteredQuery.isCompleted.map { PublicationStatus(isCompleted: $0) }) owner.selectedNovelRatingStatus.accept(owner.selectedFilteredQuery.novelRating.map { NovelRatingStatus(toFloat: $0) }) }) .disposed(by: disposeBag) @@ -228,7 +225,7 @@ final class DetailSearchViewModel: ViewModelType { }) .disposed(by: disposeBag) - input.completedButtonDidTap + input.publicationStatusButtonDidTap .subscribe(with: self, onNext: { owner, selectedCompletedStatus in if owner.selectedCompletedStatus.value == selectedCompletedStatus { owner.selectedCompletedStatus.accept(nil) @@ -237,17 +234,7 @@ final class DetailSearchViewModel: ViewModelType { } }) .disposed(by: disposeBag) - - input.novelRatingButtonDidTap - .subscribe(with: self, onNext: { owner, selectedNovelRatingStatus in - if owner.selectedNovelRatingStatus.value == selectedNovelRatingStatus { - owner.selectedNovelRatingStatus.accept(nil) - } else { - owner.selectedNovelRatingStatus.accept(selectedNovelRatingStatus) - } - }) - .disposed(by: disposeBag) - + // 키워드 input.updatedEnteredText .subscribe(with: self, onNext: { owner, text in @@ -379,8 +366,7 @@ final class DetailSearchViewModel: ViewModelType { showInfoNewImageView: showInfoNewImageView, showKeywordNewImageView: showKeywordNewImageView.asObservable(), genreListData: genreListData.asObservable(), - selectedCompletedStatus: selectedCompletedStatus.asDriver(), - selectedNovelRatingStatus: selectedNovelRatingStatus.asDriver(), + selectedPublicationStatus: selectedCompletedStatus.asDriver(), resetSelectedInfoData: resetSelectedInfoData.asObservable(), enteredText: enteredText.asObservable(), isKeywordTextFieldEditing: isKeywordTextFieldEditing.asObservable(), From 354b04ebc07fe7bcdd0e9ee623480fc0b07907b8 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Wed, 29 Apr 2026 20:42:10 +0900 Subject: [PATCH 22/37] =?UTF-8?q?[Feat]=20#695=20-=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=83=90=EC=83=89=20=EB=82=B4=20=EB=B3=84=EC=A0=90=20=ED=88=AC?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=20=EC=8A=AC=EB=9D=BC=EC=9D=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Base/WSSRangeSlider.swift | 200 ++++++++++++++++++ .../DetailSearchInfoView.swift | 85 +++++++- .../DetailSearchViewController.swift | 13 ++ .../DetailSearchViewModel.swift | 30 ++- 4 files changed, 320 insertions(+), 8 deletions(-) create mode 100644 WSSiOS/Source/Presentation/Base/WSSRangeSlider.swift diff --git a/WSSiOS/Source/Presentation/Base/WSSRangeSlider.swift b/WSSiOS/Source/Presentation/Base/WSSRangeSlider.swift new file mode 100644 index 000000000..25085b4fb --- /dev/null +++ b/WSSiOS/Source/Presentation/Base/WSSRangeSlider.swift @@ -0,0 +1,200 @@ +// +// WSSRangeSlider.swift +// WSSiOS +// +// Created by Seoyeon Choi on 4/29/26. +// + +import UIKit + +final class WSSRangeSlider: UIControl { + + //MARK: - Properties + + var minimumValue: CGFloat = 0.0 { didSet { updateLayerFrames() } } + var maximumValue: CGFloat = 5.0 { didSet { updateLayerFrames() } } + + private(set) var lowerValue: CGFloat = 0.0 { didSet { updateLayerFrames() } } + private(set) var upperValue: CGFloat = 5.0 { didSet { updateLayerFrames() } } + + var step: CGFloat = 0.5 + + private let thumbSize: CGFloat = 16 + private let trackHeight: CGFloat = 4 + + private var isDraggingLower = false + private var isDraggingUpper = false + + private let tickSize = CGSize(width: 1, height: 2) + + //MARK: - UI Components + + private let trackLayer = CALayer() + private var tickLayers: [CALayer] = [] + private let rangeLayer = CALayer() + private let lowerThumbView = UIView() + private let upperThumbView = UIView() + + //MARK: - Life Cycle + + override init(frame: CGRect) { + super.init(frame: frame) + + setUI() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var intrinsicContentSize: CGSize { + CGSize(width: UIView.noIntrinsicMetric, height: thumbSize) + } + + override func layoutSubviews() { + super.layoutSubviews() + updateLayerFrames() + } + + //MARK: - UI + + private func setUI() { + trackLayer.do { + $0.backgroundColor = UIColor.wssPrimary50.cgColor + $0.cornerRadius = trackHeight / 2 + } + layer.addSublayer(trackLayer) + + let tickCount = Int((maximumValue - minimumValue) / step) + 1 + for _ in 0.. CGFloat { + let trackWidth = bounds.width - thumbSize + return thumbSize / 2 + trackWidth * (value - minimumValue) / (maximumValue - minimumValue) + } + + private func valueForPosition(_ position: CGFloat) -> CGFloat { + let trackWidth = bounds.width - thumbSize + let ratio = (position - thumbSize / 2) / trackWidth + return minimumValue + (maximumValue - minimumValue) * max(0, min(1, ratio)) + } + + private func snapToStep(_ value: CGFloat) -> CGFloat { + (value / step).rounded() * step + } + + //MARK: - Touch Handling + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + let location = touch.location(in: self) + + let lowerDistance = abs(location.x - lowerThumbView.center.x) + let upperDistance = abs(location.x - upperThumbView.center.x) + let threshold = thumbSize * 1.5 + + if lowerValue == upperValue && lowerDistance < threshold { + if location.x >= lowerThumbView.center.x { + isDraggingUpper = true + } else { + isDraggingLower = true + } + } else if lowerDistance < upperDistance && lowerDistance < threshold { + isDraggingLower = true + } else if upperDistance < threshold { + isDraggingUpper = true + } + + return isDraggingLower || isDraggingUpper + } + + override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + let location = touch.location(in: self) + let rawValue = valueForPosition(location.x) + let snappedValue = snapToStep(rawValue) + + if isDraggingLower { + lowerValue = min(snappedValue, upperValue) + } else if isDraggingUpper { + upperValue = max(snappedValue, lowerValue) + } + + sendActions(for: .valueChanged) + return true + } + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + isDraggingLower = false + isDraggingUpper = false + } + + //MARK: - Custom Method + + func setValues(lower: CGFloat, upper: CGFloat) { + lowerValue = max(minimumValue, min(lower, maximumValue)) + upperValue = max(minimumValue, min(upper, maximumValue)) + } +} diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift index 930e62122..6cc4f43eb 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift @@ -27,6 +27,12 @@ final class DetailSearchInfoView: UIView { /// 평점 private let ratingTitleLabel = UILabel() + let ratingSlider = WSSRangeSlider() + private let ratingValueLabel = UILabel() + private let ratingMinLabelBackgroundView = UIView() + private let ratingMinLabel = UILabel() + private let ratingMaxLabelBackgroundView = UIView() + private let ratingMaxLabel = UILabel() //MARK: - Life Cycle @@ -76,16 +82,50 @@ final class DetailSearchInfoView: UIView { $0.applyWSSFont(.title2, with: StringLiterals.DetailSearch.rating) $0.textColor = .wssBlack } + + ratingValueLabel.do { + $0.applyWSSFont(.title2, with: "0.0 ~ 5.0") + $0.textColor = .wssPrimary100 + } + + ratingSlider.do { + $0.minimumValue = 0.0 + $0.maximumValue = 5.0 + $0.step = 0.5 + $0.setValues(lower: 0.0, upper: 5.0) + } + + ratingMinLabel.do { + $0.applyWSSFont(.body2, with: "0.0") + $0.textColor = .wssPrimary100 + } + + [ratingMinLabelBackgroundView, + ratingMaxLabelBackgroundView].forEach { + $0.backgroundColor = .wssGray50 + $0.layer.cornerRadius = 8 + } + + ratingMaxLabel.do { + $0.applyWSSFont(.body2, with: "5.0") + $0.textColor = .wssPrimary100 + } } private func setHierarchy() { completedStatusButtons.forEach { statusStackView.addArrangedSubview($0) } + ratingMinLabelBackgroundView.addSubview(ratingMinLabel) + ratingMaxLabelBackgroundView.addSubview(ratingMaxLabel) self.addSubviews(genreTitleLabel, genreCollectionView, statusTitleLabel, statusStackView, - ratingTitleLabel) + ratingTitleLabel, + ratingValueLabel, + ratingMinLabelBackgroundView, + ratingSlider, + ratingMaxLabelBackgroundView) } private func setLayout() { @@ -115,6 +155,40 @@ final class DetailSearchInfoView: UIView { $0.top.equalTo(statusStackView.snp.bottom).offset(42) $0.leading.equalToSuperview().inset(20) } + + ratingValueLabel.snp.makeConstraints { + $0.centerY.equalTo(ratingTitleLabel) + $0.trailing.equalToSuperview().inset(20) + } + + ratingMinLabelBackgroundView.snp.makeConstraints { + $0.top.equalTo(ratingTitleLabel.snp.bottom).offset(16) + $0.leading.equalToSuperview().inset(20) + $0.width.equalTo(50) + $0.height.equalTo(38) + + ratingMinLabel.snp.makeConstraints { + $0.center.equalToSuperview() + } + } + + ratingSlider.snp.makeConstraints { + $0.centerY.equalTo(ratingMinLabel) + $0.leading.equalTo(ratingMinLabelBackgroundView.snp.trailing).offset(17) + $0.trailing.equalTo(ratingMaxLabelBackgroundView.snp.leading).offset(-17) + $0.height.equalTo(16) + } + + ratingMaxLabelBackgroundView.snp.makeConstraints { + $0.top.equalTo(ratingMinLabelBackgroundView.snp.top) + $0.trailing.equalToSuperview().inset(20) + $0.width.equalTo(50) + $0.height.equalTo(38) + + ratingMaxLabel.snp.makeConstraints { + $0.center.equalToSuperview() + } + } } func updateCompletedKeyword(_ selectedCompletedStatus: PublicationStatus?) { @@ -128,4 +202,13 @@ final class DetailSearchInfoView: UIView { genreCollectionView.deselectItem(at: indexPath, animated: false) } } + + func updateRatingLabels(lower: CGFloat, upper: CGFloat) { + let lowerText = String(format: "%.1f", lower) + let upperText = String(format: "%.1f", upper) + ratingMinLabel.applyWSSFont(.body2, with: lowerText) + ratingMaxLabel.applyWSSFont(.body2, with: upperText) + ratingValueLabel.applyWSSFont(.body2, with: "\(lowerText) ~ \(upperText)") + ratingValueLabel.textColor = .wssPrimary100 + } } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index 6110df4e1..1c7e36064 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -94,6 +94,11 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { button.rx.tap.map { button.status } }) + let ratingSlider = rootView.detailSearchInfoView.ratingSlider + let ratingSliderValueChanged = ratingSlider.rx.controlEvent(.valueChanged) + .map { (ratingSlider.lowerValue, ratingSlider.upperValue) } + .asObservable() + let input = DetailSearchViewModel.Input( viewDidLoadEvent: viewDidLoadEvent.asObservable(), closeButtonDidTap: rootView.detailSearchHeaderView.backButton.rx.tap, @@ -105,6 +110,7 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { genreColletionViewItemSelected: rootView.detailSearchInfoView.genreCollectionView.rx.itemSelected.asObservable(), genreColletionViewItemDeselected: rootView.detailSearchInfoView.genreCollectionView.rx.itemDeselected.asObservable(), publicationStatusButtonDidTap: completedStatusButtonDidTap, + ratingSliderValueChanged: ratingSliderValueChanged, updatedEnteredText: rootView.detailSearchKeywordView.novelKeywordSelectSearchBarView.keywordTextField.rx.text.orEmpty.distinctUntilChanged().asObservable(), keywordTextFieldEditingDidBegin: rootView.detailSearchKeywordView.novelKeywordSelectSearchBarView.keywordTextField.rx.controlEvent(.editingDidBegin).asControlEvent(), keywordTextFieldEditingDidEnd: rootView.detailSearchKeywordView.novelKeywordSelectSearchBarView.keywordTextField.rx.controlEvent(.editingDidEnd).asControlEvent(), @@ -169,6 +175,13 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { }) .disposed(by: disposeBag) + output.ratingRange + .drive(with: self, onNext: { owner, range in + owner.rootView.detailSearchInfoView.ratingSlider.setValues(lower: range.0, upper: range.1) + owner.rootView.detailSearchInfoView.updateRatingLabels(lower: range.0, upper: range.1) + }) + .disposed(by: disposeBag) + output.resetSelectedInfoData .subscribe(with: self, onNext: { owner, _ in owner.rootView.detailSearchInfoView.resetAllStates() diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index 7e16269f0..89221bb2f 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -31,7 +31,8 @@ final class DetailSearchViewModel: ViewModelType { let selectedGenreListData = BehaviorRelay<[NovelGenre]>(value: []) private let genreListData = PublishRelay<[NovelGenre]>() private var selectedCompletedStatus = BehaviorRelay(value: nil) - private var selectedNovelRatingStatus = BehaviorRelay(value: nil) + private let selectedRatingLower = BehaviorRelay(value: 0.0) + private let selectedRatingUpper = BehaviorRelay(value: 5.0) private let resetSelectedInfoData = PublishRelay() // 키워드 @@ -63,6 +64,7 @@ final class DetailSearchViewModel: ViewModelType { let genreColletionViewItemSelected: Observable let genreColletionViewItemDeselected: Observable let publicationStatusButtonDidTap: Observable + let ratingSliderValueChanged: Observable<(CGFloat, CGFloat)> // 키워드 let updatedEnteredText: Observable @@ -89,6 +91,7 @@ final class DetailSearchViewModel: ViewModelType { // 정보 let genreListData: Observable<[NovelGenre]> let selectedPublicationStatus: Driver + let ratingRange: Driver<(CGFloat, CGFloat)> let resetSelectedInfoData: Observable // 키워드 @@ -123,7 +126,6 @@ final class DetailSearchViewModel: ViewModelType { owner.selectedGenreListData.accept(owner.selectedFilteredQuery.genres) owner.selectedKeywordListData.accept(owner.selectedFilteredQuery.keywords) owner.selectedCompletedStatus.accept(owner.selectedFilteredQuery.isCompleted.map { PublicationStatus(isCompleted: $0) }) - owner.selectedNovelRatingStatus.accept(owner.selectedFilteredQuery.novelRating.map { NovelRatingStatus(toFloat: $0) }) }) .disposed(by: disposeBag) @@ -167,7 +169,8 @@ final class DetailSearchViewModel: ViewModelType { owner.selectedGenreListData.accept(owner.selectedGenreList) owner.resetSelectedInfoData.accept(()) owner.selectedCompletedStatus.accept(nil) - owner.selectedNovelRatingStatus.accept(nil) + owner.selectedRatingLower.accept(0.0) + owner.selectedRatingUpper.accept(5.0) } else { // 키워드뷰 owner.selectedKeywordList = [] @@ -186,13 +189,15 @@ final class DetailSearchViewModel: ViewModelType { let keywords = owner.selectedKeywordList let genres: [NovelGenre] = owner.selectedGenreListData.value let isCompleted = owner.selectedCompletedStatus.value?.isCompleted - let novelRating = owner.selectedNovelRatingStatus.value?.toFloat - + let ratingLower = owner.selectedRatingLower.value + let ratingUpper = owner.selectedRatingUpper.value + let userInfo: [AnyHashable: Any] = [ "keywords": keywords, "genres": genres, "isCompleted": isCompleted as Any, - "novelRating": novelRating as Any + "ratingLower": ratingLower, + "ratingUpper": ratingUpper ] if owner.previousViewInfo == .search { @@ -235,6 +240,13 @@ final class DetailSearchViewModel: ViewModelType { }) .disposed(by: disposeBag) + input.ratingSliderValueChanged + .subscribe(with: self, onNext: { owner, range in + owner.selectedRatingLower.accept(range.0) + owner.selectedRatingUpper.accept(range.1) + }) + .disposed(by: disposeBag) + // 키워드 input.updatedEnteredText .subscribe(with: self, onNext: { owner, text in @@ -349,11 +361,14 @@ final class DetailSearchViewModel: ViewModelType { }) .disposed(by: disposeBag) + let ratingRange = Observable + .combineLatest(selectedRatingLower, selectedRatingUpper) + let showInfoNewImageView = Observable .combineLatest( selectedGenreListData.map { $0.count > 0 }, selectedCompletedStatus.map { $0 != nil }, - selectedNovelRatingStatus.map { $0 != nil } + ratingRange.map { $0 != 0.0 || $1 != 5.0 } ) .map { $0 || $1 || $2 } @@ -367,6 +382,7 @@ final class DetailSearchViewModel: ViewModelType { showKeywordNewImageView: showKeywordNewImageView.asObservable(), genreListData: genreListData.asObservable(), selectedPublicationStatus: selectedCompletedStatus.asDriver(), + ratingRange: ratingRange.asDriver(onErrorJustReturn: (0.0, 5.0)), resetSelectedInfoData: resetSelectedInfoData.asObservable(), enteredText: enteredText.asObservable(), isKeywordTextFieldEditing: isKeywordTextFieldEditing.asObservable(), From 5915ef4d031706bf5b66d8789e9e0fd8997be265 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 13:38:08 +0900 Subject: [PATCH 23/37] =?UTF-8?q?[Feat]=20#695=20-=20=EB=8B=A8=EC=9D=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=B7=B0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=B3=84=EC=A0=90=20=EC=8A=AC?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=8D=94=20API=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS/Network/Search/SearchService.swift | 16 +-- .../Extensions/UIViewController+.swift | 12 +- .../Data/Entity/SearchFilterOption.swift | 16 +++ .../Data/Repository/SearchRepository.swift | 57 +-------- .../HomeViewController.swift | 9 +- .../DetailSearchInfoView.swift | 2 + .../DetailSearchResultViewController.swift | 12 +- .../DetailSearchViewController.swift | 13 ++- .../DetailSearchResultViewModel.swift | 109 +++--------------- .../DetailSearchViewModel.swift | 66 ++++------- .../SearchViewController.swift | 29 +---- 11 files changed, 92 insertions(+), 249 deletions(-) create mode 100644 WSSiOS/Source/Data/Entity/SearchFilterOption.swift diff --git a/WSSiOS/Network/Search/SearchService.swift b/WSSiOS/Network/Search/SearchService.swift index 71e7b4915..c74919dd0 100644 --- a/WSSiOS/Network/Search/SearchService.swift +++ b/WSSiOS/Network/Search/SearchService.swift @@ -14,7 +14,8 @@ protocol SearchService { func searchNormalNovels(query: String, page: Int, size: Int) -> Single func searchDetailNovels(genres: [String], isCompleted: Bool?, - novelRating: Float?, + lowerNovelRating: Float, + upperNovelRating: Float, keywordIds: [Int], page: Int, size: Int) -> Single @@ -70,7 +71,8 @@ extension DefaultSearchService: SearchService { func searchDetailNovels(genres: [String], isCompleted: Bool?, - novelRating: Float?, + lowerNovelRating: Float, + upperNovelRating: Float, keywordIds: [Int], page: Int, size: Int) -> Single { @@ -79,17 +81,15 @@ extension DefaultSearchService: SearchService { URLQueryItem(name: "genres", value: genres.joined(separator: ",")), URLQueryItem(name: "keywordIds", value: keywordIds.map { String($0) }.joined(separator: ",")), URLQueryItem(name: "page", value: String(page)), - URLQueryItem(name: "size", value: String(size)) + URLQueryItem(name: "size", value: String(size)), + URLQueryItem(name: "lowerNovelRating", value: String(lowerNovelRating)), + URLQueryItem(name: "upperNovelRating", value: String(upperNovelRating)) ] if let isCompleted = isCompleted { detailSearchQueryItems.append(URLQueryItem(name: "isCompleted", value: String(isCompleted))) } - - if let novelRating = novelRating { - detailSearchQueryItems.append(URLQueryItem(name: "novelRating", value: String(novelRating))) - } - + do { let request = try makeHTTPRequest(method: .get, path: URLs.Search.detailSearch, diff --git a/WSSiOS/Resource/Extensions/UIViewController+.swift b/WSSiOS/Resource/Extensions/UIViewController+.swift index 2e85eb052..5819c7264 100644 --- a/WSSiOS/Resource/Extensions/UIViewController+.swift +++ b/WSSiOS/Resource/Extensions/UIViewController+.swift @@ -306,16 +306,14 @@ extension UIViewController { self.navigationController?.pushViewController(viewController, animated: true) } - func pushToDetailSearchViewController(selectedKeywordList: [KeywordData], - previousViewInfo: PreviousViewType, - selectedFilteredQuery: SearchFilterQuery) { + func pushToDetailSearchViewController() { let detailSearchViewController = DetailSearchViewController( viewModel: DetailSearchViewModel( keywordRepository: DefaultKeywordRepository( - keywordService: DefaultKeywordService()), - selectedKeywordList: selectedKeywordList, - previousViewInfo: previousViewInfo, - selectedFilteredQuery: selectedFilteredQuery)) + keywordService: DefaultKeywordService() + ) + ) + ) detailSearchViewController.navigationController?.isNavigationBarHidden = true detailSearchViewController.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(detailSearchViewController, diff --git a/WSSiOS/Source/Data/Entity/SearchFilterOption.swift b/WSSiOS/Source/Data/Entity/SearchFilterOption.swift new file mode 100644 index 000000000..edce8ddb6 --- /dev/null +++ b/WSSiOS/Source/Data/Entity/SearchFilterOption.swift @@ -0,0 +1,16 @@ +// +// SearchFilterOption.swift +// WSSiOS +// +// Created by Seoyeon Choi on 4/30/26. +// + +import Foundation + +struct SearchFilterQuery { + let keywords: [KeywordData] + let genres: [NovelGenre] + let isCompleted: Bool? + let lowerNovelRating: Float + let upperNovelRating: Float +} diff --git a/WSSiOS/Source/Data/Repository/SearchRepository.swift b/WSSiOS/Source/Data/Repository/SearchRepository.swift index ae0016aa2..170d18c31 100644 --- a/WSSiOS/Source/Data/Repository/SearchRepository.swift +++ b/WSSiOS/Source/Data/Repository/SearchRepository.swift @@ -14,7 +14,8 @@ protocol SearchRepository { func getSearchNovels(query: String, page: Int) -> Observable func getDetailSearchNovels(genres: [String], isCompleted: Bool?, - novelRating: Float?, + lowerNovelRating: Float, + upperNovelRating: Float, keywordIds: [Int], page: Int) -> Observable } @@ -39,62 +40,16 @@ struct DefaultSearchRepository: SearchRepository { func getDetailSearchNovels(genres: [String], isCompleted: Bool?, - novelRating: Float?, + lowerNovelRating: Float, + upperNovelRating: Float, keywordIds: [Int], page: Int) -> Observable { return searchService.searchDetailNovels(genres: genres, isCompleted: isCompleted, - novelRating: novelRating, + lowerNovelRating: lowerNovelRating, + upperNovelRating: upperNovelRating, keywordIds: keywordIds, page: page, size: searchSize).asObservable() } } - -struct TestSearchRepository: SearchRepository { - func getSosoPickNovels() -> Observable { - return Observable.just(SosoPickNovels(sosoPicks: [ - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "구리구리스"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "구리구리뱅"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "상수리 나무 아래"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "하수리 나무 위"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "하수리수리마수리"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "딱대"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "토크쇼"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "배고파"), - SosoPickNovel(novelId: 1, novelImage: "imgTest2", novelTitle: "닭가슴살꼬치먹고싶다힝구힝구힝")])) - } - - func getSearchNovels(query: String, page: Int) -> Observable { - return Observable.just(NormalSearchNovels(resultCount: 1003, isLoadable: true, novels: [ - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리리구리", novelAuthor: "구리스구리스최서연최서연구리구리구리구리구리구리구리구리구리구리구리구리", interestCount: 13, novelRating: 2.34, novelRatingCount: 221), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/70/54/70/705470563de4ad028c5323fe2ac16628.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/70/54/70/705470563de4ad028c5323fe2ac16628.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/70/54/70/705470563de4ad028c5323fe2ac16628.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21)])) - } - - func getDetailSearchNovels(genres: [String], isCompleted: Bool?, novelRating: Float?, keywordIds: [Int], page: Int) -> Observable { - return Observable.just(DetailSearchNovels(resultCount: 1116, - isLoadable: true, - novels: [SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리구리리구리", novelAuthor: "구리스구리스최서연최서연구리구리구리구리구리구리구리구리구리구리구리구리", interestCount: 13, novelRating: 2.34, novelRatingCount: 221), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/70/54/70/705470563de4ad028c5323fe2ac16628.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/70/54/70/705470563de4ad028c5323fe2ac16628.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/70/54/70/705470563de4ad028c5323fe2ac16628.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/4a/63/a3/4a63a3eda53a5d685c8593869947d06c.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21), - SearchNovel(novelId: 2, novelImage: "https://i.pinimg.com/564x/f7/8f/e1/f78fe156e361a321b5d1334e5f21f031.jpg", novelTitle: "구리구리구리구리구리구리", novelAuthor: "구리스구리스최서연최서연", interestCount: 123, novelRating: 2.34, novelRatingCount: 21)])) - } -} diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift index d5c411c1b..d06e051e8 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift @@ -223,14 +223,7 @@ final class HomeViewController: UIViewController { output.pushToDetailSearchViewController .bind(with: self, onNext: { owner, _ in - owner.pushToDetailSearchViewController( - selectedKeywordList: [], - previousViewInfo: .search, - selectedFilteredQuery: SearchFilterQuery(keywords: [], - genres: [], - isCompleted: nil, - novelRating: nil) - ) + owner.pushToDetailSearchViewController() }) .disposed(by: disposeBag) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift index 6cc4f43eb..5c0606efe 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchInfoView.swift @@ -191,6 +191,8 @@ final class DetailSearchInfoView: UIView { } } + //MARK: - Custom Method + func updateCompletedKeyword(_ selectedCompletedStatus: PublicationStatus?) { completedStatusButtons.forEach { $0.updateButton(selectedCompletedStatus: selectedCompletedStatus) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift index 7d8ca7129..9cd161bd4 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift @@ -83,10 +83,8 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele backButtonDidTap: rootView.headerView.backButton.rx.tap, novelCollectionViewContentSize: rootView.novelView.resultNovelCollectionView.rx.observe(CGSize.self, "contentSize"), novelResultCellSelected: rootView.novelView.resultNovelCollectionView.rx.itemSelected, - searchHeaderViewDidTap: rootView.headerView.backgroundView.rx.tapGesture().when(.recognized).asObservable(), viewDidLoadEvent: self.viewDidLoadEvent.asObservable(), - novelCollectionViewReachedBottom: observeReachedBottom(rootView.novelView.scrollView), - updateDetailSearchResultNotification: NotificationCenter.default.rx.notification(Notification.Name("PushToUpdateDetailSearchResult")) + novelCollectionViewReachedBottom: observeReachedBottom(rootView.novelView.scrollView) ) let output = viewModel.transform(from: input, disposeBag: disposeBag) @@ -122,14 +120,6 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele }) .disposed(by: disposeBag) - output.presentDetailSearchModal - .subscribe(with: self, onNext: { owner, data in - owner.pushToDetailSearchViewController(selectedKeywordList: data.keywords, - previousViewInfo: .resultSearchBar, - selectedFilteredQuery: data) - }) - .disposed(by: disposeBag) - output.showEmptyView .observe(on: MainScheduler.instance) .subscribe(with: self, onNext: { owner, show in diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index 1c7e36064..fb1b0b6c0 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -106,7 +106,6 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { keywordTabDidTap: rootView.detailSearchHeaderView.keywordLabel.rx.tapGesture().when(.recognized).asObservable(), resetViewDidTap: rootView.detailSearchHeaderView.resetStackView.rx.tapGesture().when(.recognized), searchNovelButtonDidTap: rootView.detailSearchButton.rx.tap, - updateDetailSearchResultData: NotificationCenter.default.rx.notification(Notification.Name("PushToUpateDetailSearchResult")).asObservable(), genreColletionViewItemSelected: rootView.detailSearchInfoView.genreCollectionView.rx.itemSelected.asObservable(), genreColletionViewItemDeselected: rootView.detailSearchInfoView.genreCollectionView.rx.itemDeselected.asObservable(), publicationStatusButtonDidTap: completedStatusButtonDidTap, @@ -151,6 +150,18 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { }) .disposed(by: disposeBag) + output.pushToResultViewController + .subscribe(with: self, onNext: { owner, filterQuery in + let viewModel = DetailSearchResultViewModel( + searchRepository: DefaultSearchRepository(searchService: DefaultSearchService()), + option: filterQuery + ) + let viewController = DetailSearchResultViewController(viewModel: viewModel) + viewController.hidesBottomBarWhenPushed = true + owner.navigationController?.pushViewController(viewController, animated: true) + }) + .disposed(by: disposeBag) + // 정보 뷰 output.genreListData .bind(to: rootView.detailSearchInfoView.genreCollectionView.rx.items(cellIdentifier: DetailSearchInfoGenreCollectionViewCell.cellIdentifier,cellType: DetailSearchInfoGenreCollectionViewCell.self)) { item, element, cell in diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift index 7e10eac5f..811771633 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift @@ -16,11 +16,8 @@ final class DetailSearchResultViewModel: ViewModelType { private let searchRepository: SearchRepository - // API 쿼리 - var keywords: [KeywordData] - var genres: [NovelGenre] - var isCompleted: Bool? - var novelRating: Float? + // 검색 필터 옵션 + var option: SearchFilterQuery // 무한 스크롤 private var currentPage: Int = 0 @@ -31,31 +28,24 @@ final class DetailSearchResultViewModel: ViewModelType { private let popViewController = PublishRelay() private let novelCollectionViewHeight = BehaviorRelay(value: 0) private let pushToNovelDetailViewController = PublishRelay() - private let presentDetailSearchModal = PublishRelay() private let showEmptyView = PublishRelay() private let filteredNovelsData = BehaviorRelay<[SearchNovel]>(value: []) private let resultCount = BehaviorRelay(value: 0) - private let updateDetailSearchResultNotification = PublishRelay() private let showLoadingView = PublishRelay() struct Input { let backButtonDidTap: ControlEvent let novelCollectionViewContentSize: Observable let novelResultCellSelected: ControlEvent - let searchHeaderViewDidTap: Observable - let viewDidLoadEvent: Observable let novelCollectionViewReachedBottom: Observable - let updateDetailSearchResultNotification: Observable } struct Output { let popViewController: Observable let novelCollectionViewHeight: Observable let pushToNovelDetailViewController: Observable - let presentDetailSearchModal: Observable - let filteredNovelsData: Observable<[SearchNovel]> let resultCount: Driver let showEmptyView: Observable @@ -63,15 +53,9 @@ final class DetailSearchResultViewModel: ViewModelType { } init(searchRepository: SearchRepository, - keywords: [KeywordData], - genres: [NovelGenre], - isCompleted: Bool?, - novelRating: Float?) { + option: SearchFilterQuery) { self.searchRepository = searchRepository - self.keywords = keywords - self.genres = genres - self.isCompleted = isCompleted - self.novelRating = novelRating + self.option = option } func transform(from input: Input, disposeBag: DisposeBag) -> Output { @@ -96,28 +80,17 @@ final class DetailSearchResultViewModel: ViewModelType { .bind(to: pushToNovelDetailViewController) .disposed(by: disposeBag) - input.searchHeaderViewDidTap - .subscribe(with: self, onNext: { owner, _ in - let filterQuery = SearchFilterQuery( - keywords: owner.keywords, - genres: owner.genres, - isCompleted: owner.isCompleted, - novelRating: owner.novelRating - ) - owner.presentDetailSearchModal.accept(filterQuery) - }) - .disposed(by: disposeBag) - input.viewDidLoadEvent .do(onNext: { self.showLoadingView.accept(true) }) .flatMapLatest { return self.getDetailSearchNovels( - genres: self.genres.map { $0.rawValue }, - isCompleted: self.isCompleted, - novelRating: self.novelRating, - keywordIds: self.keywords.map { $0.keywordId }, + genres: self.option.genres.map { $0.rawValue }, + isCompleted: self.option.isCompleted, + lowerNovelRating: self.option.lowerNovelRating, + upperNovelRating: self.option.upperNovelRating, + keywordIds: self.option.keywords.map { $0.keywordId }, page: 0 ) } @@ -146,10 +119,11 @@ final class DetailSearchResultViewModel: ViewModelType { }) .flatMapLatest { _ in self.getDetailSearchNovels( - genres: self.genres.map { $0.rawValue }, - isCompleted: self.isCompleted, - novelRating: self.novelRating, - keywordIds: self.keywords.map { $0.keywordId }, + genres: self.option.genres.map { $0.rawValue }, + isCompleted: self.option.isCompleted, + lowerNovelRating: self.option.lowerNovelRating, + upperNovelRating: self.option.upperNovelRating, + keywordIds: self.option.keywords.map { $0.keywordId }, page: self.currentPage + 1) .do(onNext: { _ in self.currentPage += 1 @@ -165,51 +139,9 @@ final class DetailSearchResultViewModel: ViewModelType { }) .disposed(by: disposeBag) - input.updateDetailSearchResultNotification - .do(onNext: { _ in - self.showLoadingView.accept(true) - }) - .subscribe(with: self, onNext: { owner, notification in - owner.updateDetailSearchResultNotification.accept(notification) - owner.filteredNovelsData.accept([]) - - if let userInfo = notification.userInfo { - let keywords = userInfo["keywords"] as? [KeywordData] - let genres = userInfo["genres"] as? [NovelGenre] - let isCompleted = userInfo["isCompleted"] as? Bool - let novelRating = userInfo["novelRating"] as? Float - - owner.keywords = keywords ?? [] - owner.genres = genres ?? [] - owner.isCompleted = isCompleted - owner.novelRating = novelRating - owner.currentPage = 0 - - owner.getDetailSearchNovels( - genres: owner.genres.map { $0.rawValue }, - isCompleted: owner.isCompleted, - novelRating: owner.novelRating, - keywordIds: owner.keywords.map { $0.keywordId }, - page: 0 - ) - .subscribe(onNext: { result in - owner.filteredNovelsData.accept(result.novels) - owner.resultCount.accept(result.resultCount) - owner.isLoadable = result.isLoadable - owner.showLoadingView.accept(false) - }, onError: { error in - print("Error fetching novels: \(error)") - owner.showLoadingView.accept(false) - }) - .disposed(by: disposeBag) - } - }) - .disposed(by: disposeBag) - return Output(popViewController: popViewController.asObservable(), novelCollectionViewHeight: novelCollectionViewHeight.asObservable(), pushToNovelDetailViewController: pushToNovelDetailViewController.asObservable(), - presentDetailSearchModal: presentDetailSearchModal.asObservable(), filteredNovelsData: filteredNovelsData.asObservable(), resultCount: resultCount.asDriver(), showEmptyView: showEmptyView.asObservable(), @@ -220,20 +152,15 @@ final class DetailSearchResultViewModel: ViewModelType { private func getDetailSearchNovels(genres: [String], isCompleted: Bool?, - novelRating: Float?, + lowerNovelRating: Float, + upperNovelRating: Float, keywordIds: [Int], page: Int) -> Observable { searchRepository.getDetailSearchNovels(genres: genres, isCompleted: isCompleted, - novelRating: novelRating, + lowerNovelRating: lowerNovelRating, + upperNovelRating: upperNovelRating, keywordIds: keywordIds, page: page) } } - -struct SearchFilterQuery { - let keywords: [KeywordData] - let genres: [NovelGenre] - let isCompleted: Bool? - let novelRating: Float? -} diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index 89221bb2f..d61be4991 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -17,14 +17,11 @@ final class DetailSearchViewModel: ViewModelType { //MARK: - Properties private let keywordRepository: KeywordRepository - private let previousViewInfo: PreviousViewType - private let selectedFilteredQuery: SearchFilterQuery - + // 전체 private let dismissModalViewController = PublishRelay() let selectedTab = BehaviorRelay(value: DetailSearchTab.info) - private let pushToDetailSearchResultViewControllerNotificationName = Notification.Name("PushToDetailSearchResult") - private let pushToUpdateDetailSearchResultViewControllerNotificationName = Notification.Name("PushToUpdateDetailSearchResult") + let pushToResultViewController = PublishRelay() // 정보 private var selectedGenreList: [NovelGenre] = [] @@ -37,7 +34,7 @@ final class DetailSearchViewModel: ViewModelType { // 키워드 var keywordSearchResultList: [KeywordData] = [] - var selectedKeywordList: [KeywordData] + var selectedKeywordList: [KeywordData] = [] let keywordLimit: Int = 20 private let enteredText = BehaviorRelay(value: "") @@ -58,7 +55,6 @@ final class DetailSearchViewModel: ViewModelType { let keywordTabDidTap: Observable let resetViewDidTap: Observable let searchNovelButtonDidTap: ControlEvent - let updateDetailSearchResultData: Observable // 정보 let genreColletionViewItemSelected: Observable @@ -87,6 +83,7 @@ final class DetailSearchViewModel: ViewModelType { let selectedTab: Driver let showInfoNewImageView: Observable let showKeywordNewImageView: Observable + let pushToResultViewController: Observable // 정보 let genreListData: Observable<[NovelGenre]> @@ -108,14 +105,8 @@ final class DetailSearchViewModel: ViewModelType { //MARK: - init - init(keywordRepository: KeywordRepository, - selectedKeywordList: [KeywordData], - previousViewInfo: PreviousViewType, - selectedFilteredQuery: SearchFilterQuery) { + init(keywordRepository: KeywordRepository) { self.keywordRepository = keywordRepository - self.selectedKeywordList = selectedKeywordList - self.previousViewInfo = previousViewInfo - self.selectedFilteredQuery = selectedFilteredQuery } func transform(from input: Input, disposeBag: DisposeBag) -> Output { @@ -123,9 +114,6 @@ final class DetailSearchViewModel: ViewModelType { input.viewDidLoadEvent .subscribe(with: self, onNext: { owner, _ in owner.genreListData.accept(NovelGenre.detailSearchGenres) - owner.selectedGenreListData.accept(owner.selectedFilteredQuery.genres) - owner.selectedKeywordListData.accept(owner.selectedFilteredQuery.keywords) - owner.selectedCompletedStatus.accept(owner.selectedFilteredQuery.isCompleted.map { PublicationStatus(isCompleted: $0) }) }) .disposed(by: disposeBag) @@ -189,31 +177,22 @@ final class DetailSearchViewModel: ViewModelType { let keywords = owner.selectedKeywordList let genres: [NovelGenre] = owner.selectedGenreListData.value let isCompleted = owner.selectedCompletedStatus.value?.isCompleted - let ratingLower = owner.selectedRatingLower.value - let ratingUpper = owner.selectedRatingUpper.value + let lowernovelRating = Float(owner.selectedRatingLower.value) + let uppernovelRating = Float(owner.selectedRatingUpper.value) - let userInfo: [AnyHashable: Any] = [ - "keywords": keywords, - "genres": genres, - "isCompleted": isCompleted as Any, - "ratingLower": ratingLower, - "ratingUpper": ratingUpper - ] - - if owner.previousViewInfo == .search { - NotificationCenter.default.post(name: owner.pushToDetailSearchResultViewControllerNotificationName, - object: nil, - userInfo: userInfo) - owner.dismissModalViewController.accept(()) - } else { - NotificationCenter.default.post(name: owner.pushToUpdateDetailSearchResultViewControllerNotificationName, - object: nil, - userInfo: userInfo) - owner.dismissModalViewController.accept(()) - } + let filterQuery = SearchFilterQuery( + keywords: keywords, + genres: genres, + isCompleted: isCompleted, + lowerNovelRating: lowernovelRating, + upperNovelRating: uppernovelRating + ) + owner.pushToResultViewController.accept(filterQuery) }) .disposed(by: disposeBag) + // MARK: - 정보 + input.genreColletionViewItemSelected .subscribe(with: self, onNext: { owner, indexPath in owner.selectedGenreList = owner.selectedGenreListData.value @@ -247,7 +226,8 @@ final class DetailSearchViewModel: ViewModelType { }) .disposed(by: disposeBag) - // 키워드 + // MARK: - 키워드 + input.updatedEnteredText .subscribe(with: self, onNext: { owner, text in owner.enteredText.accept(text) @@ -353,7 +333,7 @@ final class DetailSearchViewModel: ViewModelType { input.contactButtonDidTap .subscribe(with: self, onNext: { owner, _ in if let url = URL(string: ExternalLinks.inquiryAddNovel -) { + ) { if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:], completionHandler: nil) } @@ -380,6 +360,7 @@ final class DetailSearchViewModel: ViewModelType { selectedTab: selectedTab.asDriver(), showInfoNewImageView: showInfoNewImageView, showKeywordNewImageView: showKeywordNewImageView.asObservable(), + pushToResultViewController: pushToResultViewController.asObservable(), genreListData: genreListData.asObservable(), selectedPublicationStatus: selectedCompletedStatus.asDriver(), ratingRange: ratingRange.asDriver(onErrorJustReturn: (0.0, 5.0)), @@ -402,8 +383,3 @@ final class DetailSearchViewModel: ViewModelType { .observe(on: MainScheduler.instance) } } - -enum PreviousViewType { - case search - case resultSearchBar -} diff --git a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift index 41bdd3d55..d8d525d0c 100644 --- a/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/Search/SearchViewController/SearchViewController.swift @@ -81,39 +81,14 @@ final class SearchViewController: UIViewController { output.pushToDetailSearchViewController .bind(with: self, onNext: { owner, _ in - owner.pushToDetailSearchViewController(selectedKeywordList: [], - previousViewInfo: .search, - selectedFilteredQuery: SearchFilterQuery(keywords: [], - genres: [], - isCompleted: nil, - novelRating: nil)) + owner.pushToDetailSearchViewController() }) .disposed(by: disposeBag) output.pushToDetailSearchResultView .observe(on: MainScheduler.instance) .subscribe(with: self, onNext: { owner, notification in - if let userInfo = notification.userInfo { - let keywords = userInfo["keywords"] as? [KeywordData] - let genres = userInfo["genres"] as? [NovelGenre] - let isCompleted = userInfo["isCompleted"] as? Bool - let novelRating = userInfo["novelRating"] as? Float - - let detailSearchResultViewModel = DetailSearchResultViewModel( - searchRepository: DefaultSearchRepository(searchService: DefaultSearchService()), - keywords: keywords ?? [], - genres: genres ?? [], - isCompleted: isCompleted, - novelRating: novelRating - ) - - let detailSearchResultViewController = DetailSearchResultViewController(viewModel: detailSearchResultViewModel) - - detailSearchResultViewController.navigationController?.isNavigationBarHidden = false - detailSearchResultViewController.hidesBottomBarWhenPushed = true - - owner.navigationController?.pushViewController(detailSearchResultViewController, animated: true) - } + }) .disposed(by: disposeBag) From 7bc8cebcedf6174feaa80e4520f9c6c74f609573 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 13:46:54 +0900 Subject: [PATCH 24/37] =?UTF-8?q?[Chore]=20#695=20-=20=ED=83=90=EC=83=89?= =?UTF-8?q?=20=ED=83=AD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Presentation/Base/WSSTabBarItem.swift | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift b/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift index 790903faa..76e7548ca 100644 --- a/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift +++ b/WSSiOS/Source/Presentation/Base/WSSTabBarItem.swift @@ -10,7 +10,7 @@ import UIKit enum WSSTabBarItem: Int, CaseIterable { case home = 0 - case search, feed, library, myPage + case feed, library, myPage var normalItemImage: UIImage { switch self { @@ -18,10 +18,6 @@ enum WSSTabBarItem: Int, CaseIterable { return .icNavigateHome .withRenderingMode(.alwaysOriginal) .withTintColor(.wssGray200) - case .search: - return .icNavigateSearch - .withRenderingMode(.alwaysOriginal) - .withTintColor(.wssGray200) case .feed: return .icNavigateFeed .withRenderingMode(.alwaysOriginal) @@ -43,10 +39,6 @@ enum WSSTabBarItem: Int, CaseIterable { return .icNavigateHomeSelected .withRenderingMode(.alwaysOriginal) .withTintColor(.wssBlack) - case .search: - return .icNavigateSearchSelected - .withRenderingMode(.alwaysOriginal) - .withTintColor(.wssBlack) case .feed: return .icNavigateFeedSelected .withRenderingMode(.alwaysOriginal) @@ -66,8 +58,6 @@ enum WSSTabBarItem: Int, CaseIterable { switch self { case .home: return StringLiterals.Tabbar.Title.home - case .search: - return StringLiterals.Tabbar.Title.search case .feed: return StringLiterals.Tabbar.Title.feed case .library: @@ -91,9 +81,6 @@ enum WSSTabBarItem: Int, CaseIterable { notificationService: DefaultNotificationService()) )) - case .search: - return SearchViewController(viewModel: SearchViewModel()) - case .feed: return FeedViewController() From b3e8a2915ab211a19d9643c8f880550362817443 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 14:21:39 +0900 Subject: [PATCH 25/37] =?UTF-8?q?[Feat]=20#695=20-=20=ED=99=88=20=EA=B4=80?= =?UTF-8?q?=EC=8B=AC=EA=B8=80=20UI=20=EC=98=81=EC=97=AD=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/Home/HomeView/HomeView.swift | 9 +--- .../HomeViewController.swift | 27 +----------- .../Home/HomeViewModel/HomeViewModel.swift | 42 +------------------ .../DetailSearchHeaderView.swift | 1 + 4 files changed, 4 insertions(+), 75 deletions(-) diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeView.swift b/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeView.swift index 64063f981..597c87fa1 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeView.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeView.swift @@ -21,7 +21,6 @@ final class HomeView: UIView { let induceDetailSearchView = HomeInduceDetailSearchView() let todayPopularView = HomeTodayPopularView() let realtimePopularView = HomeRealtimePopularView() - let interestView = HomeInterestView() let tasteRecommendView = HomeTasteRecommendView() let loadingView = WSSLoadingView() @@ -60,7 +59,6 @@ final class HomeView: UIView { induceDetailSearchView, todayPopularView, realtimePopularView, - interestView, tasteRecommendView) } @@ -109,13 +107,8 @@ final class HomeView: UIView { $0.horizontalEdges.equalToSuperview() } - interestView.snp.makeConstraints { - $0.top.equalTo(realtimePopularView.snp.bottom).offset(40) - $0.horizontalEdges.equalToSuperview() - } - tasteRecommendView.snp.makeConstraints { - $0.top.equalTo(interestView.snp.bottom).offset(40) + $0.top.equalTo(realtimePopularView.snp.bottom).offset(40) $0.horizontalEdges.bottom.equalToSuperview() } } diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift index d06e051e8..bc8eedc01 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift @@ -81,11 +81,7 @@ final class HomeViewController: UIViewController { rootView.realtimePopularView.realtimePopularCollectionView.register( HomeRealtimePopularCollectionViewCell.self, forCellWithReuseIdentifier: HomeRealtimePopularCollectionViewCell.cellIdentifier) - - rootView.interestView.interestCollectionView.register( - HomeInterestCollectionViewCell.self, - forCellWithReuseIdentifier: HomeInterestCollectionViewCell.cellIdentifier) - + rootView.tasteRecommendView.tasteRecommendCollectionView.register( HomeTasteRecommendCollectionViewCell.self, forCellWithReuseIdentifier: HomeTasteRecommendCollectionViewCell.cellIdentifier) @@ -101,11 +97,9 @@ final class HomeViewController: UIViewController { viewWillAppearEvent: viewWillAppearEvent.asObservable(), viewDidLoadEvent: viewDidLoadEvent.asObservable(), todayPopularCellSelected: rootView.todayPopularView.todayPopularCollectionView.rx.itemSelected, - interestCellSelected: rootView.interestView.interestCollectionView.rx.itemSelected, tasteRecommendCellSelected: rootView.tasteRecommendView.tasteRecommendCollectionView.rx.itemSelected, tasteRecommendCollectionViewContentSize: rootView.tasteRecommendView.tasteRecommendCollectionView.rx.observe(CGSize.self, "contentSize"), announcementButtonDidTap: rootView.headerView.announcementButton.rx.tap, - registerInterestNovelButtonTapped: rootView.interestView.unregisterView.registerButton.rx.tap, setPreferredGenresButtonTapped: rootView.tasteRecommendView.unregisterView.registerButton.rx.tap, searchBarViewDidTap: rootView.searchBarView.rx.tapGesture().when(.recognized).asObservable(), indunceDetailSearchViewDidTap: rootView.induceDetailSearchView.rx.tapGesture().when(.recognized) @@ -148,25 +142,6 @@ final class HomeViewController: UIViewController { }) .disposed(by: disposeBag) - // 관심글 - output.interestList - .bind(to: rootView.interestView.interestCollectionView.rx.items( - cellIdentifier: HomeInterestCollectionViewCell.cellIdentifier, - cellType: HomeInterestCollectionViewCell.self)) { row, element, cell in - cell.bindData(data: element) - } - .disposed(by: disposeBag) - - output.updateInterestView - .observe(on: MainScheduler.instance) - .subscribe(with: self, onNext: { owner, data in - let isLogined = data.0 - let message = data.1 - let nickname = UserDefaults.standard.string(forKey: StringLiterals.UserDefault.userNickname) - owner.rootView.interestView.updateView(isLogined, message, nickname) - }) - .disposed(by: disposeBag) - output.pushToNormalSearchViewController .bind(with: self, onNext: { owner, _ in owner.pushToNormalSearchViewController() diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift b/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift index 988670990..8ee0daed1 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift @@ -30,11 +30,6 @@ final class HomeViewModel: ViewModelType { private let realtimePopularList = PublishSubject<[RealtimePopularFeed]>() private let realtimePopularDataRelay = BehaviorRelay<[[RealtimePopularFeed]]>(value: []) - // 관심글 - private let interestList = BehaviorRelay<[InterestFeed]>(value: []) - private let updateInterestView = PublishRelay<(Bool, InterestMessage)>() - private var interestFeedMessage = BehaviorRelay(value: .none) - // 취향추천 private let tasteRecommendList = BehaviorRelay<[TasteRecommendNovel]>(value: []) private let updateTasteRecommendView = PublishRelay<(Bool, Bool)>() @@ -56,11 +51,9 @@ final class HomeViewModel: ViewModelType { let viewWillAppearEvent: Observable let viewDidLoadEvent: Observable let todayPopularCellSelected: ControlEvent - let interestCellSelected: ControlEvent let tasteRecommendCellSelected: ControlEvent let tasteRecommendCollectionViewContentSize: Observable let announcementButtonDidTap: ControlEvent - let registerInterestNovelButtonTapped: ControlEvent let setPreferredGenresButtonTapped: ControlEvent let searchBarViewDidTap: Observable let indunceDetailSearchViewDidTap: Observable @@ -76,9 +69,6 @@ final class HomeViewModel: ViewModelType { var realtimePopularList: Observable<[RealtimePopularFeed]> var realtimePopularData: Observable<[[RealtimePopularFeed]]> - var interestList: Observable<[InterestFeed]> - let updateInterestView: Observable<(Bool, InterestMessage)> - var tasteRecommendList: Observable<[TasteRecommendNovel]> let tasteRecommendCollectionViewHeight: Driver let updateTasteRecommendView: Observable<(Bool, Bool)> @@ -141,16 +131,9 @@ extension HomeViewModel { let message = InterestMessage(rawValue: interestFeeds.message) if owner.isLogined { - owner.interestList.accept(interestFeeds.recommendFeeds) - owner.updateInterestView.accept((true, message ?? .none)) - owner.interestFeedMessage.accept(message ?? .none) - owner.tasteRecommendList.accept(tasteRecommendNovels.tasteNovels) owner.updateTasteRecommendView.accept((true, tasteRecommendNovels.tasteNovels.isEmpty)) } else { - owner.updateInterestView.accept((false, message ?? .none)) - owner.interestFeedMessage.accept(.none) - owner.updateTasteRecommendView.accept((false, true)) } @@ -182,8 +165,6 @@ extension HomeViewModel { UserDefaults.standard.setValue(data.userId, forKey: StringLiterals.UserDefault.userId) UserDefaults.standard.setValue(data.nickname, forKey: StringLiterals.UserDefault.userNickname) UserDefaults.standard.setValue(data.gender, forKey: StringLiterals.UserDefault.userGender) - owner.updateInterestView.accept((self.isLogined, self.interestFeedMessage.value)) - owner.getTermSetting(disposeBag: disposeBag) }) .disposed(by: disposeBag) @@ -211,15 +192,7 @@ extension HomeViewModel { } }) .disposed(by: disposeBag) - - input.interestCellSelected - .subscribe(with: self, onNext: { owner, indexPath in - AmplitudeManager.shared.track(AmplitudeEvent.Home.homeLoveFeedlist) - let novelId = owner.interestList.value[indexPath.row].novelId - owner.pushToNovelDetailViewController.accept(novelId) - }) - .disposed(by: disposeBag) - + input.tasteRecommendCellSelected .subscribe(with: self, onNext: { owner, indexPath in AmplitudeManager.shared.track(AmplitudeEvent.Home.homePreferNovellist) @@ -242,17 +215,6 @@ extension HomeViewModel { }) .disposed(by: disposeBag) - input.registerInterestNovelButtonTapped - .subscribe(with: self, onNext: { owner, _ in - AmplitudeManager.shared.track(AmplitudeEvent.Home.homeToLoveButton) - if owner.isLogined { - owner.pushToNormalSearchViewController.accept(()) - } else { - owner.showInduceLoginModalView.accept(()) - } - }) - .disposed(by: disposeBag) - input.setPreferredGenresButtonTapped .subscribe(with: self, onNext: { owner, _ in AmplitudeManager.shared.track(AmplitudeEvent.Home.homeToPreferButton) @@ -268,8 +230,6 @@ extension HomeViewModel { todayPopularList: todayPopularList.asObservable(), realtimePopularList: realtimePopularList.asObservable(), realtimePopularData: realtimePopularDataRelay.asObservable(), - interestList: interestList.asObservable(), - updateInterestView: updateInterestView.asObservable(), tasteRecommendList: tasteRecommendList.asObservable(), tasteRecommendCollectionViewHeight: tasteRecommendCollectionViewHeight.asDriver(), updateTasteRecommendView: updateTasteRecommendView.asObservable(), diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift index 14359aa71..1e3f2bd44 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchHeaderView.swift @@ -17,6 +17,7 @@ enum DetailSearchTab { final class DetailSearchHeaderView: UIView { //MARK: - UI Components + let backButton = UIButton() let infoLabel = UILabel() From 716e0be2312e8d253c520d7d93e899beec137717 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 14:30:13 +0900 Subject: [PATCH 26/37] =?UTF-8?q?[Feat]=20#695=20-=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EB=B7=B0=20=EB=82=B4=20=EC=84=9C=EC=B9=98=EB=B0=94=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20pop=20=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20StringLit?= =?UTF-8?q?erals=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resource/Constants/Strings/StringLiterals+Search.swift | 2 ++ .../DetailSearchResultHeaderView.swift | 2 +- .../DetailSearchResultViewController.swift | 3 ++- .../DetailSearchResultViewModel.swift | 7 +++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/WSSiOS/Resource/Constants/Strings/StringLiterals+Search.swift b/WSSiOS/Resource/Constants/Strings/StringLiterals+Search.swift index 2f8e0cc9a..852bfb100 100644 --- a/WSSiOS/Resource/Constants/Strings/StringLiterals+Search.swift +++ b/WSSiOS/Resource/Constants/Strings/StringLiterals+Search.swift @@ -51,5 +51,7 @@ extension StringLiterals { static let placeHolder = "키워드를 검색하세요" static let empty = "해당하는 작품이 없어요\n검색의 범위를 더 넓혀보세요" + + static let applyOption = "장르, 연재상태, 별점, 키워드 적용" } } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift index bfa28f2b8..dfb1c03e3 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift @@ -42,7 +42,7 @@ final class DetailSearchResultHeaderView: UIView { $0.layer.cornerRadius = 14 headerLabel.do { - $0.applyWSSFont(.body4, with: "장르, 연재상태, 별점, 키워드 적용") + $0.applyWSSFont(.body4, with: StringLiterals.DetailSearch.applyOption) $0.textColor = .wssGray200 } diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift index 9cd161bd4..dfcfcfa4a 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift @@ -84,7 +84,8 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele novelCollectionViewContentSize: rootView.novelView.resultNovelCollectionView.rx.observe(CGSize.self, "contentSize"), novelResultCellSelected: rootView.novelView.resultNovelCollectionView.rx.itemSelected, viewDidLoadEvent: self.viewDidLoadEvent.asObservable(), - novelCollectionViewReachedBottom: observeReachedBottom(rootView.novelView.scrollView) + novelCollectionViewReachedBottom: observeReachedBottom(rootView.novelView.scrollView), + searchBarViewDidTap: rootView.headerView.backgroundView.rx.tapGesture().when(.recognized) ) let output = viewModel.transform(from: input, disposeBag: disposeBag) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift index 811771633..1106bcd23 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchResultViewModel.swift @@ -40,6 +40,7 @@ final class DetailSearchResultViewModel: ViewModelType { let novelResultCellSelected: ControlEvent let viewDidLoadEvent: Observable let novelCollectionViewReachedBottom: Observable + let searchBarViewDidTap: Observable } struct Output { @@ -139,6 +140,12 @@ final class DetailSearchResultViewModel: ViewModelType { }) .disposed(by: disposeBag) + input.searchBarViewDidTap + .subscribe(with: self, onNext: { owner, _ in + owner.popViewController.accept(()) + }) + .disposed(by: disposeBag) + return Output(popViewController: popViewController.asObservable(), novelCollectionViewHeight: novelCollectionViewHeight.asObservable(), pushToNovelDetailViewController: pushToNovelDetailViewController.asObservable(), From 09fb475a61d717ef5b212a41b721f0ebd2651849 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 15:13:13 +0900 Subject: [PATCH 27/37] =?UTF-8?q?[Chore]=20#695=20-=20DetailSearch=20?= =?UTF-8?q?=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EC=8A=A4=EC=99=80?= =?UTF-8?q?=EC=9D=B4=ED=94=84=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailSearchResultViewController.swift | 10 ++-------- .../DetailSearchViewController.swift | 5 +++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift index dfcfcfa4a..c2b165882 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift @@ -41,10 +41,8 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - setNavigationBar() + swipeBackGesture() - AmplitudeManager.shared.track(AmplitudeEvent.Search.seekResult) } @@ -59,11 +57,7 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele viewDidLoadEvent.accept(()) } - - private func setNavigationBar() { - self.navigationController?.isNavigationBarHidden = true - } - + private func registerCell() { rootView.novelView.resultNovelCollectionView.register(HomeTasteRecommendCollectionViewCell.self, forCellWithReuseIdentifier: HomeTasteRecommendCollectionViewCell.cellIdentifier) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index fb1b0b6c0..6392466f7 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -43,6 +43,11 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { self.view = rootView } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + swipeBackGesture() + } + override func viewDidLoad() { super.viewDidLoad() From eae25f28b0e3933de9e0da7022a2b59f577853f0 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 15:47:05 +0900 Subject: [PATCH 28/37] =?UTF-8?q?[Chore]=20#695=20-=20navigationItem=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20liquid=20glass=20UI=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 및 navigationBar 불투명하게 되는 이슈사항 부분적 해결 --- WSSiOS.xcodeproj/project.pbxproj | 8 ++++---- WSSiOS/Resource/Extensions/UIViewController+.swift | 8 ++++++-- .../NovelDetailViewController.swift | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/WSSiOS.xcodeproj/project.pbxproj b/WSSiOS.xcodeproj/project.pbxproj index cc5ea0704..0ad25cd67 100644 --- a/WSSiOS.xcodeproj/project.pbxproj +++ b/WSSiOS.xcodeproj/project.pbxproj @@ -338,7 +338,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026040801; + CURRENT_PROJECT_VERSION = 2026043001; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; @@ -356,7 +356,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.6.0; + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = kr.websoso.debug2; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -383,7 +383,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026040801; + CURRENT_PROJECT_VERSION = 2026043001; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; @@ -401,7 +401,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.6.0; + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = kr.websoso; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/WSSiOS/Resource/Extensions/UIViewController+.swift b/WSSiOS/Resource/Extensions/UIViewController+.swift index 5819c7264..29f18222e 100644 --- a/WSSiOS/Resource/Extensions/UIViewController+.swift +++ b/WSSiOS/Resource/Extensions/UIViewController+.swift @@ -56,6 +56,10 @@ extension UIViewController { self.navigationItem.title = title self.navigationItem.leftBarButtonItem = left != nil ? UIBarButtonItem(customView: left!) : nil self.navigationItem.rightBarButtonItem = right != nil ? UIBarButtonItem(customView: right!) : nil + if #available(iOS 26.0, *) { + self.navigationItem.leftBarButtonItem?.hidesSharedBackground = true + self.navigationItem.rightBarButtonItem?.hidesSharedBackground = true + } setNavigationBarVisibleBeforeScroll(isVisible: isVisibleBeforeScroll) } @@ -69,7 +73,7 @@ extension UIViewController { ] $0.shadowColor = .clear } - + let whiteAppearance = UINavigationBarAppearance().then { $0.configureWithOpaqueBackground() $0.backgroundColor = .white @@ -80,7 +84,7 @@ extension UIViewController { ] $0.shadowColor = .clear } - + navigationItem.standardAppearance = whiteAppearance navigationItem.scrollEdgeAppearance = isVisible ? whiteAppearance : clearAppearance } diff --git a/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewController/NovelDetailViewController.swift b/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewController/NovelDetailViewController.swift index 3ee095cb5..2b8c8f5ce 100644 --- a/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewController/NovelDetailViewController.swift +++ b/WSSiOS/Source/Presentation/NovelDetail/NovelDetailViewController/NovelDetailViewController.swift @@ -61,7 +61,7 @@ final class NovelDetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - + registerCell() delegate() bindViewModel() @@ -74,13 +74,14 @@ final class NovelDetailViewController: UIViewController { viewWillAppearEvent.accept(()) setNavigationBar() swipeBackGesture() - self.hidesBottomBarWhenPushed = true } //MARK: - UI private func setNavigationBar() { self.setWSSNavigationBar(title: navigationTitle, left: rootView.backButton, right: rootView.headerDropDownButton, isVisibleBeforeScroll: false) + setContentScrollView(rootView.scrollView, for: .top) + self.hidesBottomBarWhenPushed = true } //MARK: - Bind From f73afbdefec3aa808ec7f12c89c1f745ba648584 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Thu, 30 Apr 2026 18:07:15 +0900 Subject: [PATCH 29/37] =?UTF-8?q?[Chore]=20#695=20-=20UserFeedListEntity?= =?UTF-8?q?=EB=82=B4=20createdDate=20=ED=8F=AC=EB=A7=B7=ED=8C=85=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS.xcodeproj/project.pbxproj | 4 ++-- .../Source/Data/Entity/UserFeedListEntity.swift | 17 +---------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/WSSiOS.xcodeproj/project.pbxproj b/WSSiOS.xcodeproj/project.pbxproj index 0ad25cd67..a5f6a1cf9 100644 --- a/WSSiOS.xcodeproj/project.pbxproj +++ b/WSSiOS.xcodeproj/project.pbxproj @@ -338,7 +338,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026043001; + CURRENT_PROJECT_VERSION = 2026043002; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; @@ -383,7 +383,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026043001; + CURRENT_PROJECT_VERSION = 2026043002; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; diff --git a/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift b/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift index 3a05f34d4..eec8f5854 100644 --- a/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift +++ b/WSSiOS/Source/Data/Entity/UserFeedListEntity.swift @@ -62,7 +62,7 @@ extension UserFeedResponse { return UserFeedEntity(feedId: self.feedId, feedContent: self.feedContent, - createdDate: self.formattedDate(), + createdDate: self.createdDate, isSpoiler: self.isSpoiler, isModified: self.isModified, isLiked: self.isLiked, @@ -78,21 +78,6 @@ extension UserFeedResponse { hasImage: hasImage, imageCount: self.imageCount) } - - private func formattedDate() -> String { - let inputDateFormatter = DateFormatter() - inputDateFormatter.dateFormat = "yyyy-MM-dd" - - guard let date = inputDateFormatter.date(from: self.createdDate) else { - return "" - } - - let outputDateFormatter = DateFormatter() - outputDateFormatter.locale = Locale(identifier: "ko_KR") - outputDateFormatter.dateFormat = "M월 d일" - - return outputDateFormatter.string(from: date) - } } struct UserFeedListItem { From 568d553306560378a21091ac52f50b14a698f4f4 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Sun, 3 May 2026 22:41:06 +0900 Subject: [PATCH 30/37] =?UTF-8?q?[Feat]=20#701=20-=20=EB=B9=84=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EC=83=81=EC=84=B8=ED=83=90?= =?UTF-8?q?=EC=83=89=20=EB=B0=B0=EB=84=88,=20=EC=9D=BC=EB=B0=98=ED=83=90?= =?UTF-8?q?=EC=83=89=20=EA=B2=B0=EA=B3=BC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EB=B9=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=8B=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/HomeViewController/HomeViewController.swift | 2 +- .../Home/Home/HomeViewModel/HomeViewModel.swift | 10 +++++++--- .../NormalSearchViewModel.swift | 12 ++++++++---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift index bc8eedc01..b45b2d752 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeViewController/HomeViewController.swift @@ -102,7 +102,7 @@ final class HomeViewController: UIViewController { announcementButtonDidTap: rootView.headerView.announcementButton.rx.tap, setPreferredGenresButtonTapped: rootView.tasteRecommendView.unregisterView.registerButton.rx.tap, searchBarViewDidTap: rootView.searchBarView.rx.tapGesture().when(.recognized).asObservable(), - indunceDetailSearchViewDidTap: rootView.induceDetailSearchView.rx.tapGesture().when(.recognized) + induceDetailSearchViewDidTap: rootView.induceDetailSearchView.rx.tapGesture().when(.recognized) ) let output = viewModel.transform(from: input, disposeBag: disposeBag) diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift b/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift index 8ee0daed1..024b00c04 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeViewModel/HomeViewModel.swift @@ -56,7 +56,7 @@ final class HomeViewModel: ViewModelType { let announcementButtonDidTap: ControlEvent let setPreferredGenresButtonTapped: ControlEvent let searchBarViewDidTap: Observable - let indunceDetailSearchViewDidTap: Observable + let induceDetailSearchViewDidTap: Observable } //MARK: - Outputs @@ -175,9 +175,13 @@ extension HomeViewModel { }) .disposed(by: disposeBag) - input.indunceDetailSearchViewDidTap + input.induceDetailSearchViewDidTap .subscribe(with: self, onNext: { owner, _ in - owner.pushToDetailSearchViewController.accept(()) + if owner.isLogined { + owner.pushToDetailSearchViewController.accept(()) + } else { + owner.showInduceLoginModalView.accept(()) + } }) .disposed(by: disposeBag) diff --git a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift index 129478b4b..084103209 100644 --- a/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/NormalSearch/NormalSearchViewModel/NormalSearchViewModel.swift @@ -17,6 +17,8 @@ final class NormalSearchViewModel: ViewModelType { private let searchRepository: SearchRepository private let disposeBag = DisposeBag() + private let isLogined = APIConstants.isLogined + // API 쿼리 private let searchText = BehaviorRelay(value: "") private var currentPage: Int = 0 @@ -34,7 +36,6 @@ final class NormalSearchViewModel: ViewModelType { private let showLoadingView = PublishRelay() // 소소픽 - private let isLogined = APIConstants.isLogined private let sosoPickList = BehaviorRelay<[SosoPickNovel]>(value: []) private let presentToInduceLoginView = PublishRelay() @@ -122,7 +123,6 @@ final class NormalSearchViewModel: ViewModelType { //MARK: - Methods func transform(from input: Input, disposeBag: DisposeBag) -> Output { - getSosoPickNovels() .subscribe(with: self, onNext: { owner, data in owner.sosoPickList.accept(data.sosoPicks) @@ -196,8 +196,12 @@ final class NormalSearchViewModel: ViewModelType { input.normalSearchCellSelected .subscribe(with: self, onNext: { owner, indexPath in AmplitudeManager.shared.track(AmplitudeEvent.Search.clickSearchResult) - let novelId = owner.normalSearchList.value[indexPath.row].novelId - owner.pushToNovelDetailViewController.accept(novelId) + if owner.isLogined { + let novelId = owner.normalSearchList.value[indexPath.row].novelId + owner.pushToNovelDetailViewController.accept(novelId) + } else { + owner.presentToInduceLoginView.accept(()) + } }) .disposed(by: disposeBag) From 28e8310643960d6586c155752ef6ee6a0eb2ea56 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Sun, 3 May 2026 22:51:05 +0900 Subject: [PATCH 31/37] =?UTF-8?q?[Feat]=20#701=20-=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=ED=83=90=EC=83=89=20=EA=B2=B0=EA=B3=BC=20=EB=B7=B0=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=ED=95=A8=EC=88=98=20=EC=B6=94=EC=B6=9C=20=EB=B0=8F?= =?UTF-8?q?=20API=20=EC=BF=BC=EB=A6=AC=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS/Network/Search/SearchService.swift | 4 ++-- WSSiOS/Resource/Extensions/UIViewController+.swift | 14 ++++++++++++++ .../DetailSearchViewController.swift | 8 +------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/WSSiOS/Network/Search/SearchService.swift b/WSSiOS/Network/Search/SearchService.swift index c74919dd0..deeac223a 100644 --- a/WSSiOS/Network/Search/SearchService.swift +++ b/WSSiOS/Network/Search/SearchService.swift @@ -82,8 +82,8 @@ extension DefaultSearchService: SearchService { URLQueryItem(name: "keywordIds", value: keywordIds.map { String($0) }.joined(separator: ",")), URLQueryItem(name: "page", value: String(page)), URLQueryItem(name: "size", value: String(size)), - URLQueryItem(name: "lowerNovelRating", value: String(lowerNovelRating)), - URLQueryItem(name: "upperNovelRating", value: String(upperNovelRating)) + URLQueryItem(name: "novelRatingStart", value: String(lowerNovelRating)), + URLQueryItem(name: "novelRatingEnd", value: String(upperNovelRating)) ] if let isCompleted = isCompleted { diff --git a/WSSiOS/Resource/Extensions/UIViewController+.swift b/WSSiOS/Resource/Extensions/UIViewController+.swift index 29f18222e..3e1c22303 100644 --- a/WSSiOS/Resource/Extensions/UIViewController+.swift +++ b/WSSiOS/Resource/Extensions/UIViewController+.swift @@ -324,6 +324,20 @@ extension UIViewController { animated: true) } + func pushToDetailSearchResultViewController(option: SearchFilterQuery) { + let detailSearchResultViewController = DetailSearchResultViewController( + viewModel: DetailSearchResultViewModel( + searchRepository: DefaultSearchRepository( + searchService: DefaultSearchService()), + option: option + ) + ) + detailSearchResultViewController.navigationController?.isNavigationBarHidden = true + detailSearchResultViewController.hidesBottomBarWhenPushed = true + self.navigationController?.pushViewController(detailSearchResultViewController, + animated: true) + } + func presentInduceLoginViewController() { let viewController = InduceLoginViewController() viewController.modalPresentationStyle = .overFullScreen diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift index 6392466f7..232d23ec7 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchViewController.swift @@ -157,13 +157,7 @@ final class DetailSearchViewController: UIViewController, UIScrollViewDelegate { output.pushToResultViewController .subscribe(with: self, onNext: { owner, filterQuery in - let viewModel = DetailSearchResultViewModel( - searchRepository: DefaultSearchRepository(searchService: DefaultSearchService()), - option: filterQuery - ) - let viewController = DetailSearchResultViewController(viewModel: viewModel) - viewController.hidesBottomBarWhenPushed = true - owner.navigationController?.pushViewController(viewController, animated: true) + owner.pushToDetailSearchResultViewController(option: filterQuery) }) .disposed(by: disposeBag) From 01b1f09f725301514331e0d78e3f27d029a628fc Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Sun, 3 May 2026 23:06:52 +0900 Subject: [PATCH 32/37] =?UTF-8?q?[Fix]=20#701=20-=20=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9=EB=90=9C=20NovelGenre=20=EA=B0=92=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailSearchViewModel/DetailSearchViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index d61be4991..1a17d97fd 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -196,7 +196,7 @@ final class DetailSearchViewModel: ViewModelType { input.genreColletionViewItemSelected .subscribe(with: self, onNext: { owner, indexPath in owner.selectedGenreList = owner.selectedGenreListData.value - owner.selectedGenreList.append(NovelGenre.allCases[indexPath.row]) + owner.selectedGenreList.append(NovelGenre.detailSearchGenres[indexPath.row]) owner.selectedGenreListData.accept(owner.selectedGenreList) }) .disposed(by: disposeBag) From 34481051a198a835709e0310e2af1e7dc0a77517 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Sun, 3 May 2026 23:07:27 +0900 Subject: [PATCH 33/37] =?UTF-8?q?[Feat]=20#701=20-=20=EC=84=A0=ED=83=9D?= =?UTF-8?q?=ED=95=9C=20=EC=98=B5=EC=85=98=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=B7=B0=20UX=EB=9D=BC=EC=9D=B4=ED=8C=85?= =?UTF-8?q?=EA=B0=92=20=EB=B3=80=EA=B2=BD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailSearchResultHeaderView.swift | 26 ++++++++++++++++--- .../DetailSearchResultViewController.swift | 3 ++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift index dfb1c03e3..dbb1aac17 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchView/DetailSearchAssistantView/DetailSearchResultHeaderView.swift @@ -36,21 +36,39 @@ final class DetailSearchResultHeaderView: UIView { backButton.do { $0.setImage(.icNavigateLeft.withRenderingMode(.alwaysOriginal).withTintColor(.wssBlack), for: .normal) } - + backgroundView.do { $0.backgroundColor = .wssGray50 $0.layer.cornerRadius = 14 - + headerLabel.do { - $0.applyWSSFont(.body4, with: StringLiterals.DetailSearch.applyOption) + $0.applyWSSFont(.body4, with: "별점 적용") $0.textColor = .wssGray200 } - + controllerImageView.do { $0.image = .icController.withRenderingMode(.alwaysOriginal).withTintColor(.wssBlack) } } } + + func updateHeaderLabel(with option: SearchFilterQuery) { + var appliedFilters: [String] = [] + + if !option.genres.isEmpty { + appliedFilters.append(StringLiterals.DetailSearch.genre) + } + if option.isCompleted != nil { + appliedFilters.append(StringLiterals.DetailSearch.serialStatus) + } + appliedFilters.append(StringLiterals.DetailSearch.rating) + if !option.keywords.isEmpty { + appliedFilters.append(StringLiterals.DetailSearch.keyword) + } + + let text = appliedFilters.joined(separator: ", ") + " 적용" + headerLabel.applyWSSFont(.body4, with: text) + } private func setHierarchy() { backgroundView.addSubviews(headerLabel, diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift index c2b165882..4d6836611 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift @@ -54,7 +54,8 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele bindViewModel() bindAction() - + + rootView.headerView.updateHeaderLabel(with: viewModel.option) viewDidLoadEvent.accept(()) } From 4afe5b997d559b09fc9d7e21b230fc461521dd87 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Sun, 10 May 2026 00:45:37 +0900 Subject: [PATCH 34/37] =?UTF-8?q?[Fix]=20#701=20-=20=EC=9E=A5=EB=A5=B4=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=A0=81=EC=9A=A9=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=EC=82=AC=ED=95=AD=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS.xcodeproj/project.pbxproj | 4 ++-- .../DetailSearchViewModel/DetailSearchViewModel.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WSSiOS.xcodeproj/project.pbxproj b/WSSiOS.xcodeproj/project.pbxproj index a5f6a1cf9..507b2d350 100644 --- a/WSSiOS.xcodeproj/project.pbxproj +++ b/WSSiOS.xcodeproj/project.pbxproj @@ -338,7 +338,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026043002; + CURRENT_PROJECT_VERSION = 2026050301; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; @@ -383,7 +383,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026043002; + CURRENT_PROJECT_VERSION = 2026050301; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift index 1a17d97fd..ccc259418 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewModel/DetailSearchViewModel.swift @@ -204,7 +204,7 @@ final class DetailSearchViewModel: ViewModelType { input.genreColletionViewItemDeselected .subscribe(with: self, onNext: { owner, indexPath in owner.selectedGenreList = owner.selectedGenreListData.value - owner.selectedGenreList.removeAll { $0 == NovelGenre.allCases[indexPath.row] } + owner.selectedGenreList.removeAll { $0 == NovelGenre.detailSearchGenres[indexPath.row] } owner.selectedGenreListData.accept(owner.selectedGenreList) }) .disposed(by: disposeBag) From c47f54a27f10cafd1eed6405d6877a5f50300269 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Sun, 10 May 2026 00:51:47 +0900 Subject: [PATCH 35/37] =?UTF-8?q?[Chore]=20#701=20-=20=ED=99=88=20-=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=ED=83=90=EC=83=89=20=EC=9C=A0=EB=8F=84=20?= =?UTF-8?q?=EB=B0=B0=EB=84=88=20=EB=B7=B0=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Strings/StringLiterals+Home.swift | 2 +- .../HomeInduceDetailSearchView.swift | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/WSSiOS/Resource/Constants/Strings/StringLiterals+Home.swift b/WSSiOS/Resource/Constants/Strings/StringLiterals+Home.swift index 0a8777d13..186dbfa2b 100644 --- a/WSSiOS/Resource/Constants/Strings/StringLiterals+Home.swift +++ b/WSSiOS/Resource/Constants/Strings/StringLiterals+Home.swift @@ -15,7 +15,7 @@ extension StringLiterals { static let interest = "님의 관심글" static let notLoggedInInterest = "・ :*관심글*: ・" static let recommend = "이 웹소설은 어때요? (´ヮ`)ノ📚" - static let detailSearchBanner = "뭐 읽을지 고민될 땐 ?" + static let detailSearchBanner = "뭐 읽을지 고민될 때" } enum SubTitle { diff --git a/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeAssistantView/HomeInduceDetailSearchView.swift b/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeAssistantView/HomeInduceDetailSearchView.swift index f121f917c..54625d9f9 100644 --- a/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeAssistantView/HomeInduceDetailSearchView.swift +++ b/WSSiOS/Source/Presentation/Home/Home/HomeView/HomeAssistantView/HomeInduceDetailSearchView.swift @@ -17,7 +17,9 @@ final class HomeInduceDetailSearchView: UIView { private let imageView = UIImageView() private let labelStackView = UIStackView() + private let titleStackView = UIStackView() private let titleLabel = UILabel() + private let titleNavigateImageView = UIImageView() private let subTitleLabel = UILabel() //MARK: - Life Cycle @@ -52,9 +54,29 @@ final class HomeInduceDetailSearchView: UIView { $0.spacing = 4 } + titleStackView.do { + $0.axis = .horizontal + $0.spacing = 6 + $0.alignment = .firstBaseline + } + titleLabel.do { $0.applyWSSFont(.title1, with: StringLiterals.Home.Title.detailSearchBanner) $0.textColor = .wssBlack + + $0.setContentHuggingPriority(.required, for: .horizontal) + } + + titleNavigateImageView.do { + $0.image = .icNavigateRight + .withRenderingMode(.alwaysOriginal) + .withTintColor(.wssBlack) + + $0.contentMode = .left + $0.clipsToBounds = false + + $0.setContentHuggingPriority(.defaultLow, for: .horizontal) + $0.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) } subTitleLabel.do { @@ -66,8 +88,10 @@ final class HomeInduceDetailSearchView: UIView { private func setHierachy() { self.addSubviews(imageView, labelStackView) - labelStackView.addArrangedSubviews(titleLabel, + labelStackView.addArrangedSubviews(titleStackView, subTitleLabel) + titleStackView.addArrangedSubviews (titleLabel, + titleNavigateImageView) } private func setLayout() { @@ -79,7 +103,7 @@ final class HomeInduceDetailSearchView: UIView { $0.trailing.equalToSuperview() $0.bottom.equalToSuperview().offset(14) } - + labelStackView.snp.makeConstraints { $0.top.equalToSuperview().inset(24) $0.leading.equalToSuperview().inset(20) From a1c3a37ea8eff6fac3c2e3a24c9f67962323093b Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Mon, 11 May 2026 16:20:25 +0900 Subject: [PATCH 36/37] =?UTF-8?q?[Chore]=20#701=20-=20=EC=9E=91=ED=92=88?= =?UTF-8?q?=20=ED=8F=89=EA=B0=80=20=EB=B7=B0=20=EA=B8=B0=ED=9A=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20UI=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Constants/Strings/StringLiterals+Novel.swift | 2 +- .../LibraryFilterAttractivePointOptionButton.swift | 8 ++++---- .../NovelReviewRatingView.swift | 5 +++++ .../NovelReviewStatusView.swift | 3 ++- .../NovelReviewStatusCollectionViewCell.swift | 13 ++++++++----- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift b/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift index d7b89bf6c..204ed4f39 100644 --- a/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift +++ b/WSSiOS/Resource/Constants/Strings/StringLiterals+Novel.swift @@ -100,7 +100,7 @@ extension StringLiterals { } enum Date { - static let addDate = "날짜 추가" + static let addDate = "본 날짜 추가" static let complete = "완료" static let removeDate = "날짜 삭제" static let startDate = "시작 날짜" diff --git a/WSSiOS/Source/Presentation/Library/LibraryFilter/LibraryFilterView/LibraryFilterAssistantView/LibraryFilterAttractivePointOptionButton.swift b/WSSiOS/Source/Presentation/Library/LibraryFilter/LibraryFilterView/LibraryFilterAssistantView/LibraryFilterAttractivePointOptionButton.swift index 7824b38c2..1494f4f0c 100644 --- a/WSSiOS/Source/Presentation/Library/LibraryFilter/LibraryFilterView/LibraryFilterAssistantView/LibraryFilterAttractivePointOptionButton.swift +++ b/WSSiOS/Source/Presentation/Library/LibraryFilter/LibraryFilterView/LibraryFilterAssistantView/LibraryFilterAttractivePointOptionButton.swift @@ -49,13 +49,13 @@ final class LibraryFilterAttractivePointOptionButton: UIButton { statusLabel.do { $0.applyWSSFont(.body4, with: attractivePoint.koreanString) - $0.textColor = .wssGray300 + $0.textColor = .wssGray200 $0.isUserInteractionEnabled = false } statusImageView.do { $0.image = attractivePoint.image.withRenderingMode(.alwaysTemplate) - $0.tintColor = .wssGray100 + $0.tintColor = .wssGray80 $0.contentMode = .scaleAspectFit $0.isUserInteractionEnabled = false } @@ -86,7 +86,7 @@ final class LibraryFilterAttractivePointOptionButton: UIButton { func updateButton(selectedOptions: [AttractivePoint]) { let isSelected = selectedOptions.contains(where: {$0 == self.attractivePoint}) - statusImageView.tintColor = isSelected ? .wssPrimary100 : .wssGray100 - statusLabel.textColor = isSelected ? .wssPrimary100 : .wssGray300 + statusImageView.tintColor = isSelected ? .wssPrimary100 : .wssGray80 + statusLabel.textColor = isSelected ? .wssPrimary100 : .wssGray200 } } diff --git a/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewRatingView.swift b/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewRatingView.swift index 15bfb3b2c..25c9fb7bd 100644 --- a/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewRatingView.swift +++ b/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewRatingView.swift @@ -43,6 +43,7 @@ final class NovelReviewRatingView: UIView { $0.applyWSSFont(.title3, with: StringLiterals.NovelReview.rating) $0.textColor = .wssBlack } + starImageStackView.do { $0.axis = .horizontal $0.spacing = 10 @@ -84,6 +85,8 @@ final class NovelReviewRatingView: UIView { let starImageView = UIImageView().then { $0.isUserInteractionEnabled = true $0.image = .icLargeStarEmpty + .withRenderingMode(.alwaysOriginal) + .withTintColor(.wssGray80) $0.contentMode = .scaleAspectFill $0.clipsToBounds = true } @@ -103,6 +106,8 @@ final class NovelReviewRatingView: UIView { imageView.image = .icLargeStarHalf default: imageView.image = .icLargeStarEmpty + .withRenderingMode(.alwaysOriginal) + .withTintColor(.wssGray80) } } } diff --git a/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewStatusView.swift b/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewStatusView.swift index 4fc4832bb..a1afbb704 100644 --- a/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewStatusView.swift +++ b/WSSiOS/Source/Presentation/NovelReview/NovelReviewView/NovelReviewAssistantView/NovelReviewStatusView.swift @@ -14,7 +14,8 @@ final class NovelReviewStatusView: UIView { //MARK: - Components - let statusCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()) + let statusCollectionView = UICollectionView(frame: .zero, + collectionViewLayout: UICollectionViewLayout()) let dateLabel = UILabel() private let dateFormatter = DateFormatter() diff --git a/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelReviewStatusCollectionViewCell.swift b/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelReviewStatusCollectionViewCell.swift index 850ed4d0f..8cbf67f49 100644 --- a/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelReviewStatusCollectionViewCell.swift +++ b/WSSiOS/Source/Presentation/NovelReview/NovelReviewViewCell/NovelReviewStatusCollectionViewCell.swift @@ -44,7 +44,7 @@ final class NovelReviewStatusCollectionViewCell: UICollectionViewCell { private func setUI() { statusImageView.do { $0.contentMode = .scaleAspectFit - $0.tintColor = .wssGray200 + $0.tintColor = .wssGray80 } titleLabel.do { @@ -77,7 +77,8 @@ final class NovelReviewStatusCollectionViewCell: UICollectionViewCell { switch status { case .watching: statusImageView.do { - $0.image = UIImage(resource: .icNovelReviewWatching).withRenderingMode(.alwaysTemplate) + $0.image = UIImage(resource: .icNovelReviewWatching) + .withRenderingMode(.alwaysTemplate) } titleLabel.do { @@ -85,7 +86,8 @@ final class NovelReviewStatusCollectionViewCell: UICollectionViewCell { } case .watched: statusImageView.do { - $0.image = UIImage(resource: .icNovelReviewWatched).withRenderingMode(.alwaysTemplate) + $0.image = UIImage(resource: .icNovelReviewWatched) + .withRenderingMode(.alwaysTemplate) } titleLabel.do { @@ -93,7 +95,8 @@ final class NovelReviewStatusCollectionViewCell: UICollectionViewCell { } case .quit: statusImageView.do { - $0.image = UIImage(resource: .icNovelReviewQuit).withRenderingMode(.alwaysTemplate) + $0.image = UIImage(resource: .icNovelReviewQuit) + .withRenderingMode(.alwaysTemplate) } titleLabel.do { @@ -106,7 +109,7 @@ final class NovelReviewStatusCollectionViewCell: UICollectionViewCell { private func updateTintColor(isSelected: Bool) { statusImageView.do { - $0.tintColor = isSelected ? .wssPrimary100 : .wssGray200 + $0.tintColor = isSelected ? .wssPrimary100 : .wssGray80 } titleLabel.do { From 392ca0371755e957de08a9362fac4c55d85b0e80 Mon Sep 17 00:00:00 2001 From: Seoyeon Choi Date: Mon, 11 May 2026 23:11:36 +0900 Subject: [PATCH 37/37] =?UTF-8?q?[Fix]=20#701-=20DetailSearchResultVC=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EB=B0=94=20hidden=EC=9D=84=20viewWillAppear?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WSSiOS.xcodeproj/project.pbxproj | 4 ++-- .../DetailSearchResultViewController.swift | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/WSSiOS.xcodeproj/project.pbxproj b/WSSiOS.xcodeproj/project.pbxproj index 507b2d350..fdce52ad9 100644 --- a/WSSiOS.xcodeproj/project.pbxproj +++ b/WSSiOS.xcodeproj/project.pbxproj @@ -338,7 +338,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026050301; + CURRENT_PROJECT_VERSION = 2026051101; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; @@ -383,7 +383,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026050301; + CURRENT_PROJECT_VERSION = 2026051101; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 9SVDHQS4M3; GENERATE_INFOPLIST_FILE = YES; diff --git a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift index 4d6836611..2ed3910b3 100644 --- a/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift +++ b/WSSiOS/Source/Presentation/Search/DetailSearch/DetailSearchViewController/DetailSearchResultViewController.swift @@ -44,6 +44,9 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele swipeBackGesture() AmplitudeManager.shared.track(AmplitudeEvent.Search.seekResult) + + self.navigationController?.setNavigationBarHidden(true, + animated: true) } override func viewDidLoad() {