From d8f42562021fe7ed0a778f0ccc984e358153bb7d Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 15 May 2025 19:01:45 +0900 Subject: [PATCH 01/20] =?UTF-8?q?chore/#150:=20git=20ignore=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 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 3dd82707..f586473e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output + +# Cursor +Poppool/buildServer.json From 7cd9a174256af300f15539d116f0bcd5ec74e3c2 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Thu, 15 May 2025 19:49:49 +0900 Subject: [PATCH 02/20] =?UTF-8?q?chore/#150:=20.gitignore=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 --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 3dd82707..4449f4b9 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,8 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output + +# Cursor +Poppool/buildServer.json +.vscode/* + From c13e8d4917100d951da10c8d012984e6aa8a6bc6 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 16 May 2025 00:59:16 +0900 Subject: [PATCH 03/20] =?UTF-8?q?feat/#150:=20safe=20index=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=9C=20Collection=20extention=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 --- .../Infrastructure/Extension/Collection+.swift | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift diff --git a/Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift b/Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift new file mode 100644 index 00000000..a4b22fc3 --- /dev/null +++ b/Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift @@ -0,0 +1,7 @@ +import Foundation + +public extension Collection { + subscript(safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} From b6a1c04cbe2c1af7f84c589823eea5b965961f8a Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 16 May 2025 01:43:18 +0900 Subject: [PATCH 04/20] =?UTF-8?q?refactor/#150:=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EC=97=86=EC=9D=8C=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Factory/PopupSearchLayoutFactory.swift | 2 +- .../Reactor/PopupSearchReactor.swift | 24 +++++++++---------- ...SearchResultEmptyCollectionViewCell.swift} | 6 ++--- .../PopupSearch/View/PopupSearchView.swift | 12 +++++----- .../View/PopupSearchViewController.swift | 4 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) rename Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/{SearchResultEmptyTitleCollectionViewCell.swift => SearchResultEmptyCollectionViewCell.swift} (84%) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index 77e6d8e7..d9048589 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -27,7 +27,7 @@ final class PopupSearchLayoutFactory { case .searchResult: let sectionSnapshot = dataSource.snapshot(for: sectionType) let hasEmptyItem = sectionSnapshot.items.contains { item in - if case .searchResultEmptyTitle = item { return true } + if case .searchResultEmptyItem = item { return true } return false } return makeSearchResultSectionLayout(hasEmptyItem: hasEmptyItem) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift index 646443d1..aa24bd77 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift @@ -48,7 +48,7 @@ public final class PopupSearchReactor: Reactor { case updateClearButtonIsHidden(to: Bool) case updateCurrentPage(to: Int32) case updateSearchingState(to: Bool) - case updateSearchResultEmptyTitle + case updateSearchResultEmpty case updateSearchResultBookmark(indexPath: IndexPath) case updateSearchResultDataSource @@ -68,7 +68,7 @@ public final class PopupSearchReactor: Reactor { var categoryItems: [TagModel] = [] var searchResultItems: [SearchResultModel] = [] var searchResultHeader: SearchResultHeaderModel = SearchResultHeaderModel(filterText: Filter.shared.title) - var searchResultEmptyTitle: String? + var searchResultEmpty: String? @Pulse var searchBarText: String? = nil @Pulse var present: PresentTarget? @@ -119,7 +119,7 @@ public final class PopupSearchReactor: Reactor { .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), .just(.setupSearchResultTotalPageCount(count: response.totalPages)), .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmptyTitle), + .just(.updateSearchResultEmpty), .just(.updateSearchResultDataSource) ]) } @@ -142,7 +142,7 @@ public final class PopupSearchReactor: Reactor { .just(.setupSearchResultTotalPageCount(count: 0)), // FIXME: API에 해당 결과값이 아직 없음 .just(.updateCurrentPage(to: 0)), .just(.updateSearchingState(to: true)), - .just(.updateSearchResultEmptyTitle), + .just(.updateSearchResultEmpty), .just(.updateClearButtonIsHidden(to: true)), .just(.updateEditingState), .just(.updateSearchResultDataSource) @@ -174,7 +174,7 @@ public final class PopupSearchReactor: Reactor { .just(.setupSearchResultTotalPageCount(count: response.totalPages)), .just(.updateCurrentPage(to: 0)), .just(.updateSearchingState(to: false)), - .just(.updateSearchResultEmptyTitle), + .just(.updateSearchResultEmpty), .just(.updateSearchBar(to: nil)), .just(.updateEditingState), .just(.updateSearchResultDataSource) @@ -199,7 +199,7 @@ public final class PopupSearchReactor: Reactor { .just(.updateCurrentPage(to: 0)), .just(.updateSearchBar(to: keyword)), .just(.updateSearchingState(to: true)), - .just(.updateSearchResultEmptyTitle), + .just(.updateSearchResultEmpty), .just(.updateClearButtonIsHidden(to: true)), .just(.updateEditingState), .just(.updateSearchResultDataSource) @@ -231,7 +231,7 @@ public final class PopupSearchReactor: Reactor { .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), .just(.setupSearchResultTotalPageCount(count: response.totalPages)), .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmptyTitle), + .just(.updateSearchResultEmpty), .just(.updateSearchResultDataSource) ]) } @@ -250,7 +250,7 @@ public final class PopupSearchReactor: Reactor { .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), .just(.setupSearchResultTotalPageCount(count: response.totalPages)), .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmptyTitle), + .just(.updateSearchResultEmpty), .just(.updateSearchResultDataSource) ]) } @@ -269,7 +269,7 @@ public final class PopupSearchReactor: Reactor { .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), .just(.setupSearchResultTotalPageCount(count: response.totalPages)), .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmptyTitle), + .just(.updateSearchResultEmpty), .just(.updateSearchResultDataSource) ]) } @@ -335,8 +335,8 @@ public final class PopupSearchReactor: Reactor { case .updateSearchingState(let isSearching): newState.isSearching = isSearching - case .updateSearchResultEmptyTitle: - newState.searchResultEmptyTitle = makeSearchResultEmptyTitle(state: newState) + case .updateSearchResultEmpty: + newState.searchResultEmpty = makeSearchResultEmpty(state: newState) case .updateSearchResultBookmark(let indexPath): newState.searchResultItems[indexPath.item].isBookmark.toggle() @@ -437,7 +437,7 @@ private extension PopupSearchReactor { ) } - func makeSearchResultEmptyTitle(state: State) -> String? { + func makeSearchResultEmpty(state: State) -> String? { if !currentState.searchResultItems.isEmpty { return nil } else if currentState.isSearching { return "검색 결과가 없어요 :(\n다른 키워드로 검색해주세요" } else { return "검색 결과가 없어요 :(\n다른 옵션을 선택해주세요" } } diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyTitleCollectionViewCell.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyCollectionViewCell.swift similarity index 84% rename from Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyTitleCollectionViewCell.swift rename to Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyCollectionViewCell.swift index 7d3786fa..9a82752c 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyTitleCollectionViewCell.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyCollectionViewCell.swift @@ -6,7 +6,7 @@ import Infrastructure import SnapKit import Then -final class SearchResultEmptyTitleCollectionViewCell: UICollectionViewCell { +final class SearchResultEmptyCollectionViewCell: UICollectionViewCell { // MARK: - Properties private let emptyLabel = PPLabel( @@ -34,7 +34,7 @@ final class SearchResultEmptyTitleCollectionViewCell: UICollectionViewCell { } // MARK: - SetUp -private extension SearchResultEmptyTitleCollectionViewCell { +private extension SearchResultEmptyCollectionViewCell { func addViews() { [emptyLabel].forEach { self.addSubview($0) @@ -52,7 +52,7 @@ private extension SearchResultEmptyTitleCollectionViewCell { func configureUI() { } } -extension SearchResultEmptyTitleCollectionViewCell { +extension SearchResultEmptyCollectionViewCell { func configureCell(title: String) { self.emptyLabel.text = title } diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift index 2c1421be..5aba2eab 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift @@ -59,8 +59,8 @@ final class PopupSearchView: UIView { ) $0.register( - SearchResultEmptyTitleCollectionViewCell.self, - forCellWithReuseIdentifier: SearchResultEmptyTitleCollectionViewCell.identifiers + SearchResultEmptyCollectionViewCell.self, + forCellWithReuseIdentifier: SearchResultEmptyCollectionViewCell.identifiers ) // UICollectionView 최 상/하단 빈 영역 @@ -206,11 +206,11 @@ extension PopupSearchView { return cell - case .searchResultEmptyTitle(let title): + case .searchResultEmptyItem(let title): let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: SearchResultEmptyTitleCollectionViewCell.identifiers, + withReuseIdentifier: SearchResultEmptyCollectionViewCell.identifiers, for: indexPath - ) as! SearchResultEmptyTitleCollectionViewCell + ) as! SearchResultEmptyCollectionViewCell cell.configureCell(title: title) @@ -314,7 +314,7 @@ extension PopupSearchView { case categoryItem(TagModel) case searchResultHeaderItem(SearchResultHeaderModel) case searchResultItem(SearchResultModel) - case searchResultEmptyTitle(String) + case searchResultEmptyItem(String) } /// Section의 헤더를 구분하기 위한 변수 diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift index 45262a60..0433b134 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift @@ -205,11 +205,11 @@ extension PopupSearchViewController { .withLatestFrom(reactor.state) .withUnretained(self) .subscribe { (owner, state) in - if let emptyTitle = state.searchResultEmptyTitle { + if let searchResultEmpty = state.searchResultEmpty { owner.mainView.updateSearchResultSectionSnapshot( with: state.searchResultItems.map(PopupSearchView.SectionItem.searchResultItem), header: PopupSearchView.SectionItem.searchResultHeaderItem(state.searchResultHeader), - empty: PopupSearchView.SectionItem.searchResultEmptyTitle(emptyTitle) + empty: PopupSearchView.SectionItem.searchResultEmptyItem(searchResultEmpty) ) } else { owner.mainView.updateSearchResultSectionSnapshot( From e18f5720d77c12499ecca39c609f719fdeb85151 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 16 May 2025 01:46:01 +0900 Subject: [PATCH 05/20] =?UTF-8?q?refactor/#150:=20View=EC=99=80=20Reactor?= =?UTF-8?q?=20=EA=B3=B5=ED=86=B5=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=A0=20Section=20type=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Factory/PopupSearchLayoutFactory.swift | 2 +- .../PopupSearch/SectionType/Section.swift | 8 ++++++++ .../PopupSearch/View/PopupSearchView.swift | 16 ++++------------ 3 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index d9048589..a8f9ff0c 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -4,7 +4,7 @@ import UIKit final class PopupSearchLayoutFactory { func makeCollectionViewLayout( - dataSourceProvider: @escaping () -> UICollectionViewDiffableDataSource? + dataSourceProvider: @escaping () -> UICollectionViewDiffableDataSource? ) -> UICollectionViewLayout { return UICollectionViewCompositionalLayout(sectionProvider: { [weak self] sectionIndex, _ -> NSCollectionLayoutSection? in guard let self = self, diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift new file mode 100644 index 00000000..9ff211b5 --- /dev/null +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift @@ -0,0 +1,8 @@ +import Foundation + +enum PopupSearchSection: CaseIterable, Hashable { + case recentSearch + case category + case searchResultHeader + case searchResult +} diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift index 5aba2eab..5c3fde23 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift @@ -11,7 +11,7 @@ import Then final class PopupSearchView: UIView { // MARK: - Properties - private var dataSource: UICollectionViewDiffableDataSource? + private var dataSource: UICollectionViewDiffableDataSource? private let layoutFactory: PopupSearchLayoutFactory = PopupSearchLayoutFactory() let recentSearchTagRemoveButtonTapped = PublishRelay() @@ -118,7 +118,7 @@ private extension PopupSearchView { extension PopupSearchView { private func configurationDataSourceItem() { self.dataSource = UICollectionViewDiffableDataSource< - PopupSearchView.Section, + PopupSearchSection, PopupSearchView.SectionItem >( collectionView: collectionView @@ -252,7 +252,7 @@ extension PopupSearchView { } } - func updateSectionSnapshot(at section: Section, with items: [SectionItem]) { + func updateSectionSnapshot(at section: PopupSearchSection, with items: [SectionItem]) { if items.isEmpty { guard var snapshot = dataSource?.snapshot() else { return } snapshot.deleteSections([section]) @@ -293,21 +293,13 @@ extension PopupSearchView { dataSource?.apply(snapshot, animatingDifferences: false) } - func getSectionsFromDataSource() -> [Section] { + func getSectionsFromDataSource() -> [PopupSearchSection] { return dataSource?.snapshot().sectionIdentifiers ?? [] } } // MARK: - Section information extension PopupSearchView { - /// View를 구성하는 section을 정의 - enum Section: CaseIterable, Hashable { - case recentSearch - case category - case searchResultHeader - case searchResult - } - /// Section에 들어갈 Item을 정의한 변수 enum SectionItem: Hashable { case recentSearchItem(TagModel) From 123ccf772a673c13fd72bb631c66b800850a8b4d Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 16 May 2025 02:08:02 +0900 Subject: [PATCH 06/20] =?UTF-8?q?refactor/#150:=20=EB=B9=88=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=9D=BC=EB=95=8C=EC=9D=98=20Section=EA=B3=BC=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SearchResult Section을 유동적으로 사용하던것을 분리함 - 이로 인해 dataSource의 의존성을 줄임 --- .../Factory/PopupSearchLayoutFactory.swift | 43 ++++++++++++++----- .../Reactor/PopupSearchReactor.swift | 4 +- .../PopupSearch/SectionType/Section.swift | 1 + .../PopupSearch/View/PopupSearchView.swift | 14 +++--- .../View/PopupSearchViewController.swift | 26 +++++------ 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index a8f9ff0c..fb1cc401 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -25,12 +25,10 @@ final class PopupSearchLayoutFactory { return makeSearchResultHeaderSectionLayout() case .searchResult: - let sectionSnapshot = dataSource.snapshot(for: sectionType) - let hasEmptyItem = sectionSnapshot.items.contains { item in - if case .searchResultEmptyItem = item { return true } - return false - } - return makeSearchResultSectionLayout(hasEmptyItem: hasEmptyItem) + return makeSearchResultSectionLayout() + + case .searchResultEmpty: + return makeSearchResultEmptySectionLayout() } }) } @@ -95,12 +93,10 @@ final class PopupSearchLayoutFactory { return section } - func makeSearchResultSectionLayout(hasEmptyItem: Bool) -> NSCollectionLayoutSection { - let itemWidth: NSCollectionLayoutDimension = hasEmptyItem ? .fractionalWidth(1.0) : .fractionalWidth(0.5) - + func makeSearchResultSectionLayout() -> NSCollectionLayoutSection { // Item let itemSize = NSCollectionLayoutSize( - widthDimension: itemWidth, + widthDimension: .fractionalWidth(0.5), heightDimension: .absolute(249) ) let item = NSCollectionLayoutItem(layoutSize: itemSize) @@ -124,6 +120,33 @@ final class PopupSearchLayoutFactory { return section } + func makeSearchResultEmptySectionLayout() -> NSCollectionLayoutSection { + + // Item + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalHeight(1.0) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + // Group + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalHeight(1.0) + ) + + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item] + ) + + // Section + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20) + + return section + } + func makeTagCollectionHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem { // Header let headerSize = NSCollectionLayoutSize( diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift index aa24bd77..70f9a939 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift @@ -438,7 +438,9 @@ private extension PopupSearchReactor { } func makeSearchResultEmpty(state: State) -> String? { - if !currentState.searchResultItems.isEmpty { return nil } else if currentState.isSearching { return "검색 결과가 없어요 :(\n다른 키워드로 검색해주세요" } else { return "검색 결과가 없어요 :(\n다른 옵션을 선택해주세요" } + if !currentState.searchResultItems.isEmpty { return nil } + else if currentState.isSearching { return "검색 결과가 없어요 :(\n다른 키워드로 검색해주세요" } + else { return "검색 결과가 없어요 :(\n다른 옵션을 선택해주세요" } } /// 받침에 따라 이/가 를 판단해서 붙여준다. diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift index 9ff211b5..bd858660 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift @@ -5,4 +5,5 @@ enum PopupSearchSection: CaseIterable, Hashable { case category case searchResultHeader case searchResult + case searchResultEmpty } diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift index 5c3fde23..e02d7f64 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift @@ -278,16 +278,18 @@ extension PopupSearchView { empty: SectionItem? = nil ) { guard var snapshot = dataSource?.snapshot() else { return } - - snapshot.deleteSections([.searchResultHeader, .searchResult]) - - snapshot.appendSections( [.searchResultHeader, .searchResult]) - snapshot.appendItems([header], toSection: .searchResultHeader) + snapshot.deleteSections([.searchResultHeader, .searchResult, .searchResultEmpty]) if let empty { - snapshot.appendItems([empty], toSection: .searchResult) + snapshot.appendSections([.searchResultHeader, .searchResultEmpty]) + snapshot.appendItems([header], toSection: .searchResultHeader) + snapshot.appendItems([empty], toSection: .searchResultEmpty) + collectionView.isScrollEnabled = false } else { + snapshot.appendSections([.searchResultHeader, .searchResult]) + snapshot.appendItems([header], toSection: .searchResultHeader) snapshot.appendItems(items, toSection: .searchResult) + collectionView.isScrollEnabled = true } dataSource?.apply(snapshot, animatingDifferences: false) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift index 0433b134..10ca1787 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift @@ -93,12 +93,18 @@ extension PopupSearchViewController { switch sections[indexPath.section] { case .recentSearch: return Reactor.Action.recentSearchTagButtonTapped(indexPath: indexPath) + case .category: return Reactor.Action.categoryTagButtonTapped - case .searchResultHeader: return nil + case .searchResultHeader: + return nil + case .searchResult: return Reactor.Action.searchResultItemTapped(indexPath: indexPath) + + case .searchResultEmpty: + return nil } } .bind(to: reactor.action) @@ -205,18 +211,12 @@ extension PopupSearchViewController { .withLatestFrom(reactor.state) .withUnretained(self) .subscribe { (owner, state) in - if let searchResultEmpty = state.searchResultEmpty { - owner.mainView.updateSearchResultSectionSnapshot( - with: state.searchResultItems.map(PopupSearchView.SectionItem.searchResultItem), - header: PopupSearchView.SectionItem.searchResultHeaderItem(state.searchResultHeader), - empty: PopupSearchView.SectionItem.searchResultEmptyItem(searchResultEmpty) - ) - } else { - owner.mainView.updateSearchResultSectionSnapshot( - with: state.searchResultItems.map(PopupSearchView.SectionItem.searchResultItem), - header: PopupSearchView.SectionItem.searchResultHeaderItem(state.searchResultHeader) - ) - } + owner.mainView.updateSearchResultSectionSnapshot( + with: state.searchResultItems.map(PopupSearchView.SectionItem.searchResultItem), + header: PopupSearchView.SectionItem.searchResultHeaderItem(state.searchResultHeader), + empty: state.searchResultEmpty == nil ? nil : + PopupSearchView.SectionItem.searchResultEmptyItem(state.searchResultEmpty!) + ) } .disposed(by: disposeBag) } From 7ad04b938da1c4e6e82845cf8bbff9ff627e69f3 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 16 May 2025 15:11:58 +0900 Subject: [PATCH 07/20] =?UTF-8?q?refactor/#150:=20=EB=A6=AC=EC=95=A1?= =?UTF-8?q?=ED=84=B0=20=EC=BD=94=EB=93=9C=20=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reactor/PopupSearchReactor.swift | 313 +++++++++--------- 1 file changed, 160 insertions(+), 153 deletions(-) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift index 70f9a939..40f81bae 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift @@ -109,193 +109,55 @@ public final class PopupSearchReactor: Reactor { public func mutate(action: Action) -> Observable { switch action { case .viewDidLoad: - return fetchSearchResult() - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return Observable.concat([ - .just(.setupRecentSearch(items: owner.makeRecentSearchItems())), - .just(.setupCategory(items: owner.makeCategoryItems())), - .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), - .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), - .just(.setupSearchResultTotalPageCount(count: response.totalPages)), - .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmpty), - .just(.updateSearchResultDataSource) - ]) - } + return handleViewDidLoad() case .searchBarEditing(let text): - return .just(.updateClearButtonIsHidden(to: text.isEmpty ? true : false)) + return handleSearchBarEditing(text) case .searchBarExitEditing(let text): - return fetchSearchResult(keyword: text) - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return Observable.concat([ - .just(.setupRecentSearch(items: [])), - .just(.setupCategory(items: [])), - .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popupStoreList, response.loginYn))), - .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput( - keyword: owner.makePostPositionedText(text), - count: Int64(response.popupStoreList.count) - ))), // FIXME: API에 해당 결과값이 아직 없음 - .just(.setupSearchResultTotalPageCount(count: 0)), // FIXME: API에 해당 결과값이 아직 없음 - .just(.updateCurrentPage(to: 0)), - .just(.updateSearchingState(to: true)), - .just(.updateSearchResultEmpty), - .just(.updateClearButtonIsHidden(to: true)), - .just(.updateEditingState), - .just(.updateSearchResultDataSource) - ]) - } + return handleSearchBarExitEditing(text) case .searchBarEndEditing: - return .concat([ - .just(.updateClearButtonIsHidden(to: true)), - .just(.updateEditingState) - ]) + return handleSearchBarEndEditing() case .searchBarClearButtonTapped: - return Observable.concat([ - .just(.updateClearButtonIsHidden(to: true)), - .just(.updateSearchBar(to: nil)) - ]) + return handleSearchBarClear() case .searchBarCancelButtonTapped: - if currentState.isSearching { - return fetchSearchResult() - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return Observable.concat([ - .just(.setupRecentSearch(items: owner.makeRecentSearchItems())), - .just(.setupCategory(items: owner.makeCategoryItems())), - .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), - .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), - .just(.setupSearchResultTotalPageCount(count: response.totalPages)), - .just(.updateCurrentPage(to: 0)), - .just(.updateSearchingState(to: false)), - .just(.updateSearchResultEmpty), - .just(.updateSearchBar(to: nil)), - .just(.updateEditingState), - .just(.updateSearchResultDataSource) - ]) - } - } else { return .just(.present(target: .before)) } + return handleSearchBarCancel() case .recentSearchTagButtonTapped(let indexPath): - let keyword = self.findRecentSearchKeyword(at: indexPath) - return fetchSearchResult(keyword: keyword) - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return Observable.concat([ - .just(.setupRecentSearch(items: [])), - .just(.setupCategory(items: [])), - .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popupStoreList, response.loginYn))), - .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput( - keyword: owner.makePostPositionedText(keyword), - count: Int64(response.popupStoreList.count) - ))), - .just(.setupSearchResultTotalPageCount(count: 0)), // FIXME: API에 해당 결과값이 아직 없음 - .just(.updateCurrentPage(to: 0)), - .just(.updateSearchBar(to: keyword)), - .just(.updateSearchingState(to: true)), - .just(.updateSearchResultEmpty), - .just(.updateClearButtonIsHidden(to: true)), - .just(.updateEditingState), - .just(.updateSearchResultDataSource) - ]) - } + return handleRecentSearchTagTap(at: indexPath) case .recentSearchTagRemoveButtonTapped(let text): - self.removeRecentSearchItem(text: text) - return Observable.concat([ - .just(.setupRecentSearch(items: self.makeRecentSearchItems())), - .just(.updateSearchResultDataSource) - ]) + return handleRecentSearchTagRemove(text) case .recentSearchTagRemoveAllButtonTapped: - self.removeAllRecentSearchItems() - return .concat([ - .just(.setupRecentSearch(items: self.makeRecentSearchItems())), - .just(.updateSearchResultDataSource) - ]) + return handleRecentSearchTagRemoveAll() case .categoryTagRemoveButtonTapped(let categoryID): - self.removeCategoryItem(by: categoryID) - return fetchSearchResult() - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return Observable.concat([ - .just(.setupCategory(items: owner.makeCategoryItems())), - .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), - .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), - .just(.setupSearchResultTotalPageCount(count: response.totalPages)), - .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmpty), - .just(.updateSearchResultDataSource) - ]) - } + return handleCategoryTagRemove(categoryID) case .categoryTagButtonTapped: return .just(.present(target: .categorySelector)) case .categoryChangedBySelector: - return fetchSearchResult() - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return .concat([ - .just(.setupRecentSearch(items: owner.makeRecentSearchItems())), - .just(.setupCategory(items: owner.makeCategoryItems())), - .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), - .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), - .just(.setupSearchResultTotalPageCount(count: response.totalPages)), - .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmpty), - .just(.updateSearchResultDataSource) - ]) - } + return handleCategoryChanged() case .searchResultFilterButtonTapped: return .just(.present(target: .filterSelector)) case .searchResultFilterChangedBySelector: - return fetchSearchResult() - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return .concat([ - .just(.setupRecentSearch(items: owner.makeRecentSearchItems())), - .just(.setupCategory(items: owner.makeCategoryItems())), - .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), - .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), - .just(.setupSearchResultTotalPageCount(count: response.totalPages)), - .just(.updateCurrentPage(to: 0)), - .just(.updateSearchResultEmpty), - .just(.updateSearchResultDataSource) - ]) - } + return handleFilterChanged() case .searchResultItemTapped(let indexPath): - guard let popupID = self.findPopupStoreID(at: indexPath) else { return .empty() } - return .just(.present(target: .popupDetail(popupID: popupID))) + return handleSearchResultItemTap(at: indexPath) case .searchResultBookmarkButtonTapped(let indexPath): - return fetchSearchResultBookmark(at: indexPath) - .andThen(.concat([ - .just(.updateSearchResultBookmark(indexPath: indexPath)), - .just(.updateSearchResultDataSource) - ])) + return handleSearchResultBookmark(at: indexPath) case .searchResultPrefetchItems(let indexPathList): - guard isPrefetchable(indexPathList: indexPathList) else { return .empty() } - return fetchSearchResult(page: currentState.currentPage + 1) - .withUnretained(self) - .flatMap { (owner, response) -> Observable in - return .concat([ - .just(.appendSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), - .just(.updateCurrentPage(to: owner.currentState.currentPage + 1)), - .just(.updateSearchResultDataSource) - ]) - } + return handleSearchResultPrefetch(at: indexPathList) } } @@ -488,3 +350,148 @@ private extension PopupSearchReactor { return isScrollToEnd && hasNextPage } } + + +// MARK: - Mutate Handlers +private extension PopupSearchReactor { + func handleViewDidLoad() -> Observable { + return loadDefaultSearchResults() + } + + func handleSearchBarEditing(_ text: String) -> Observable { + return .just(.updateClearButtonIsHidden(to: !text.isEmpty)) + } + + func handleSearchBarExitEditing(_ text: String) -> Observable { + return loadKeywordSearchResults(text) + } + + func handleSearchBarEndEditing() -> Observable { + return Observable.concat([ + .just(.updateClearButtonIsHidden(to: true)), + .just(.updateEditingState) + ]) + } + + func handleSearchBarClear() -> Observable { + return Observable.concat([ + .just(.updateClearButtonIsHidden(to: true)), + .just(.updateSearchBar(to: nil)) + ]) + } + + func handleSearchBarCancel() -> Observable { + if currentState.isSearching { + return loadDefaultSearchResults() + } else { + return .just(.present(target: .before)) + } + } + + func handleRecentSearchTagTap(at indexPath: IndexPath) -> Observable { + let keyword = findRecentSearchKeyword(at: indexPath) + return loadKeywordSearchResults(keyword) + } + + func handleRecentSearchTagRemove(_ text: String) -> Observable { + removeRecentSearchItem(text: text) + return Observable.concat([ + .just(.setupRecentSearch(items: makeRecentSearchItems())), + .just(.updateSearchResultDataSource) + ]) + } + + func handleRecentSearchTagRemoveAll() -> Observable { + removeAllRecentSearchItems() + return Observable.concat([ + .just(.setupRecentSearch(items: makeRecentSearchItems())), + .just(.updateSearchResultDataSource) + ]) + } + + func handleCategoryTagRemove(_ categoryID: Int) -> Observable { + removeCategoryItem(by: categoryID) + return loadDefaultSearchResults() + } + + func handleCategoryChanged() -> Observable { + return loadDefaultSearchResults() + } + + func handleFilterChanged() -> Observable { + return loadDefaultSearchResults() + } + + func handleSearchResultItemTap(at indexPath: IndexPath) -> Observable { + guard let popupID = findPopupStoreID(at: indexPath) else { return .empty() } + return .just(.present(target: .popupDetail(popupID: popupID))) + } + + func handleSearchResultBookmark(at indexPath: IndexPath) -> Observable { + return fetchSearchResultBookmark(at: indexPath) + .andThen(.concat([ + .just(.updateSearchResultBookmark(indexPath: indexPath)), + .just(.updateSearchResultDataSource) + ])) + } + + func handleSearchResultPrefetch(at indexPathList: [IndexPath]) -> Observable { + guard isPrefetchable(prefetchCount: 4, indexPathList: indexPathList) else { return .empty() } + return fetchSearchResult(page: currentState.currentPage + 1) + .withUnretained(self) + .flatMap { owner, response in + Observable.concat([ + .just(.appendSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), + .just(.updateCurrentPage(to: owner.currentState.currentPage + 1)), + .just(.updateSearchResultDataSource) + ]) + } + } +} + +// MARK: - Load Search Results +private extension PopupSearchReactor { + func loadDefaultSearchResults(page: Int32 = 0) -> Observable { + return fetchSearchResult(page: page) + .withUnretained(self) + .flatMap { owner, response in + Observable.concat([ + .just(.setupRecentSearch(items: owner.makeRecentSearchItems())), + .just(.setupCategory(items: owner.makeCategoryItems())), + .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput(count: response.totalElements))), + .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), + .just(.setupSearchResultTotalPageCount(count: response.totalPages)), + .just(.updateCurrentPage(to: 0)), + .just(.updateSearchingState(to: false)), + .just(.updateSearchResultEmpty), + .just(.updateSearchBar(to: nil)), + .just(.updateEditingState), + .just(.updateSearchResultDataSource) + ]) + } + } + + func loadKeywordSearchResults(_ keyword: String?) -> Observable { + guard let keyword = keyword else { return .empty() } + return fetchSearchResult(keyword: keyword) + .withUnretained(self) + .flatMap { owner, response in + Observable.concat([ + .just(.setupRecentSearch(items: [])), + .just(.setupCategory(items: [])), + .just(.setupSearchResult(items: owner.makeSearchResultItems(response.popupStoreList, response.loginYn))), + .just(.setupSearchResultHeader(item: owner.makeSearchResultHeaderInput( + keyword: owner.makePostPositionedText(keyword), + count: Int64(response.popupStoreList.count) + ))), + .just(.setupSearchResultTotalPageCount(count: 0)), + .just(.updateCurrentPage(to: 0)), + .just(.updateSearchingState(to: true)), + .just(.updateSearchResultEmpty), + .just(.updateClearButtonIsHidden(to: true)), + .just(.updateEditingState), + .just(.updateSearchResultDataSource) + ]) + } + } +} From 269afcedb3864561958074bcf4d0bf2685522abb Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 16 May 2025 15:12:40 +0900 Subject: [PATCH 08/20] =?UTF-8?q?rafactor/#150:=20state=20=EB=8B=A8?= =?UTF-8?q?=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reactor/PopupSearchReactor.swift | 27 +++++++------------ .../View/PopupSearchViewController.swift | 8 +++--- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift index 40f81bae..82535e44 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift @@ -48,9 +48,8 @@ public final class PopupSearchReactor: Reactor { case updateClearButtonIsHidden(to: Bool) case updateCurrentPage(to: Int32) case updateSearchingState(to: Bool) - case updateSearchResultEmpty case updateSearchResultBookmark(indexPath: IndexPath) - case updateSearchResultDataSource + case updateSearchResultSection case present(target: PresentTarget) } @@ -68,13 +67,12 @@ public final class PopupSearchReactor: Reactor { var categoryItems: [TagModel] = [] var searchResultItems: [SearchResultModel] = [] var searchResultHeader: SearchResultHeaderModel = SearchResultHeaderModel(filterText: Filter.shared.title) - var searchResultEmpty: String? @Pulse var searchBarText: String? = nil @Pulse var present: PresentTarget? @Pulse var clearButtonIsHidden: Bool? @Pulse var endEditing: Void? - @Pulse var updateSearchResultDataSource: Void? + @Pulse var updateSearchResultSection: String? @Pulse var dismiss: Void? fileprivate var isSearching: Bool = false @@ -197,14 +195,11 @@ public final class PopupSearchReactor: Reactor { case .updateSearchingState(let isSearching): newState.isSearching = isSearching - case .updateSearchResultEmpty: - newState.searchResultEmpty = makeSearchResultEmpty(state: newState) - case .updateSearchResultBookmark(let indexPath): newState.searchResultItems[indexPath.item].isBookmark.toggle() - case .updateSearchResultDataSource: - newState.updateSearchResultDataSource = () + case .updateSearchResultSection: + newState.updateSearchResultSection = makeSearchResultEmpty(state: newState) case .present(let target): newState.present = target @@ -397,7 +392,7 @@ private extension PopupSearchReactor { removeRecentSearchItem(text: text) return Observable.concat([ .just(.setupRecentSearch(items: makeRecentSearchItems())), - .just(.updateSearchResultDataSource) + .just(.updateSearchResultSection) ]) } @@ -405,7 +400,7 @@ private extension PopupSearchReactor { removeAllRecentSearchItems() return Observable.concat([ .just(.setupRecentSearch(items: makeRecentSearchItems())), - .just(.updateSearchResultDataSource) + .just(.updateSearchResultSection) ]) } @@ -431,7 +426,7 @@ private extension PopupSearchReactor { return fetchSearchResultBookmark(at: indexPath) .andThen(.concat([ .just(.updateSearchResultBookmark(indexPath: indexPath)), - .just(.updateSearchResultDataSource) + .just(.updateSearchResultSection) ])) } @@ -443,7 +438,7 @@ private extension PopupSearchReactor { Observable.concat([ .just(.appendSearchResult(items: owner.makeSearchResultItems(response.popUpStoreList, response.loginYn))), .just(.updateCurrentPage(to: owner.currentState.currentPage + 1)), - .just(.updateSearchResultDataSource) + .just(.updateSearchResultSection) ]) } } @@ -463,10 +458,9 @@ private extension PopupSearchReactor { .just(.setupSearchResultTotalPageCount(count: response.totalPages)), .just(.updateCurrentPage(to: 0)), .just(.updateSearchingState(to: false)), - .just(.updateSearchResultEmpty), .just(.updateSearchBar(to: nil)), .just(.updateEditingState), - .just(.updateSearchResultDataSource) + .just(.updateSearchResultSection) ]) } } @@ -487,10 +481,9 @@ private extension PopupSearchReactor { .just(.setupSearchResultTotalPageCount(count: 0)), .just(.updateCurrentPage(to: 0)), .just(.updateSearchingState(to: true)), - .just(.updateSearchResultEmpty), .just(.updateClearButtonIsHidden(to: true)), .just(.updateEditingState), - .just(.updateSearchResultDataSource) + .just(.updateSearchResultSection) ]) } } diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift index 10ca1787..f95cb7d0 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift @@ -207,15 +207,17 @@ extension PopupSearchViewController { } .disposed(by: disposeBag) - reactor.pulse(\.$updateSearchResultDataSource) + reactor.pulse(\.$updateSearchResultSection) .withLatestFrom(reactor.state) .withUnretained(self) .subscribe { (owner, state) in + let isEmpty = state.updateSearchResultSection == nil + let emptyCaseTitle = state.updateSearchResultSection + owner.mainView.updateSearchResultSectionSnapshot( with: state.searchResultItems.map(PopupSearchView.SectionItem.searchResultItem), header: PopupSearchView.SectionItem.searchResultHeaderItem(state.searchResultHeader), - empty: state.searchResultEmpty == nil ? nil : - PopupSearchView.SectionItem.searchResultEmptyItem(state.searchResultEmpty!) + empty: isEmpty ? nil : PopupSearchView.SectionItem.searchResultEmptyItem(emptyCaseTitle!) ) } .disposed(by: disposeBag) From 8bf7e35b4841ae7a29695409b85ddc4b7f0e4716 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Fri, 16 May 2025 15:19:58 +0900 Subject: [PATCH 09/20] =?UTF-8?q?fix/#150:=20clear=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9D=B4=20=EB=B0=98=EB=8C=80=EB=A1=9C=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=ED=95=98=EB=8D=98=20=EB=B6=80=EB=B6=84=20=EC=9B=90=EC=83=81?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift index 82535e44..fde9cc33 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift @@ -354,7 +354,7 @@ private extension PopupSearchReactor { } func handleSearchBarEditing(_ text: String) -> Observable { - return .just(.updateClearButtonIsHidden(to: !text.isEmpty)) + return .just(.updateClearButtonIsHidden(to: text.isEmpty)) } func handleSearchBarExitEditing(_ text: String) -> Observable { From eada1e17b6aff85c05d4c20dce82808d3de81a40 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sat, 17 May 2025 01:10:22 +0900 Subject: [PATCH 10/20] =?UTF-8?q?fix/#150:=20RxCocoa=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SearchFeature.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature.xcodeproj/project.pbxproj b/Poppool/PresentationLayer/SearchFeature/SearchFeature.xcodeproj/project.pbxproj index 339781c4..e600b653 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature.xcodeproj/project.pbxproj +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0506BE882DD79A6C006CDEDE /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 0506BE872DD79A6C006CDEDE /* RxCocoa */; }; 052413092DCF7DA100C42E2D /* DesignSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 052413082DCF7DA100C42E2D /* DesignSystem.framework */; }; 0524130A2DCF7DA100C42E2D /* DesignSystem.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 052413082DCF7DA100C42E2D /* DesignSystem.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 054A96202DCE38B500C0DD58 /* SearchFeatureInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05734BF52DCDA6B90093825D /* SearchFeatureInterface.framework */; }; @@ -40,7 +41,6 @@ 05CFFBF42DCB908B0051129F /* DesignSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05EC23422DC49AA200C761A5 /* DesignSystem.framework */; }; 05CFFBF52DCB908B0051129F /* DesignSystem.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 05EC23422DC49AA200C761A5 /* DesignSystem.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 05CFFBF72DCB90A10051129F /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 05CFFBF62DCB90A10051129F /* SnapKit */; }; - 05EC233C2DC49A7600C761A5 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 05EC233B2DC49A7600C761A5 /* RxCocoa */; }; 05EC233E2DC49A7600C761A5 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 05EC233D2DC49A7600C761A5 /* RxSwift */; }; 05EC23412DC49A8B00C761A5 /* ReactorKit in Frameworks */ = {isa = PBXBuildFile; productRef = 05EC23402DC49A8B00C761A5 /* ReactorKit */; }; 05EC23472DC49AA800C761A5 /* DomainInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05EC23462DC49AA800C761A5 /* DomainInterface.framework */; }; @@ -178,6 +178,7 @@ files = ( 05734C442DCDF7240093825D /* PresentationInterface.framework in Frameworks */, 05EC2AE92DC7C07400C761A5 /* RxRelay in Frameworks */, + 0506BE882DD79A6C006CDEDE /* RxCocoa in Frameworks */, 05EC285F2DC5C1CF00C761A5 /* DesignSystem.framework in Frameworks */, 05EC23472DC49AA800C761A5 /* DomainInterface.framework in Frameworks */, 05EC234B2DC49AB400C761A5 /* Then in Frameworks */, @@ -185,7 +186,6 @@ 05EC234E2DC49AC100C761A5 /* SnapKit in Frameworks */, 05734C082DCDA7D20093825D /* SearchFeatureInterface.framework in Frameworks */, 05EC23412DC49A8B00C761A5 /* ReactorKit in Frameworks */, - 05EC233C2DC49A7600C761A5 /* RxCocoa in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -306,12 +306,12 @@ ); name = SearchFeature; packageProductDependencies = ( - 05EC233B2DC49A7600C761A5 /* RxCocoa */, 05EC233D2DC49A7600C761A5 /* RxSwift */, 05EC23402DC49A8B00C761A5 /* ReactorKit */, 05EC234A2DC49AB400C761A5 /* Then */, 05EC234D2DC49AC100C761A5 /* SnapKit */, 05EC2AE82DC7C07400C761A5 /* RxRelay */, + 0506BE872DD79A6C006CDEDE /* RxCocoa */, ); productName = SearchFeature; productReference = 0516336D2DC457A900A6C0D1 /* SearchFeature.framework */; @@ -938,6 +938,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 0506BE872DD79A6C006CDEDE /* RxCocoa */ = { + isa = XCSwiftPackageProductDependency; + package = 05EC233A2DC49A7600C761A5 /* XCRemoteSwiftPackageReference "RxSwift" */; + productName = RxCocoa; + }; 054A96252DCE38E900C0DD58 /* Tabman */ = { isa = XCSwiftPackageProductDependency; package = 054A96242DCE38E900C0DD58 /* XCRemoteSwiftPackageReference "Tabman" */; @@ -973,11 +978,6 @@ package = 05EC234C2DC49AC100C761A5 /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; - 05EC233B2DC49A7600C761A5 /* RxCocoa */ = { - isa = XCSwiftPackageProductDependency; - package = 05EC233A2DC49A7600C761A5 /* XCRemoteSwiftPackageReference "RxSwift" */; - productName = RxCocoa; - }; 05EC233D2DC49A7600C761A5 /* RxSwift */ = { isa = XCSwiftPackageProductDependency; package = 05EC233A2DC49A7600C761A5 /* XCRemoteSwiftPackageReference "RxSwift" */; From c68ef47483a449c32d4d055f7417b24487fa9437 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sat, 17 May 2025 01:11:36 +0900 Subject: [PATCH 11/20] =?UTF-8?q?refactor/#150:=20Fatory=EB=A5=BC=20value?= =?UTF-8?q?=20semantic=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PopupSearch/Factory/PopupSearchLayoutFactory.swift | 2 +- .../SearchFeature/PopupSearch/View/PopupSearchView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index fb1cc401..b79662f1 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -1,7 +1,6 @@ import UIKit // MARK: - Layout -final class PopupSearchLayoutFactory { func makeCollectionViewLayout( dataSourceProvider: @escaping () -> UICollectionViewDiffableDataSource? @@ -9,6 +8,7 @@ final class PopupSearchLayoutFactory { return UICollectionViewCompositionalLayout(sectionProvider: { [weak self] sectionIndex, _ -> NSCollectionLayoutSection? in guard let self = self, let dataSource = dataSourceProvider() else { return nil } +struct PopupSearchLayoutFactory { // sectionIndex를 사용하여 현재 dataSource에서 Section 타입을 가져옴 guard sectionIndex < dataSource.snapshot().numberOfSections, diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift index e02d7f64..9ea87db4 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift @@ -12,7 +12,7 @@ final class PopupSearchView: UIView { // MARK: - Properties private var dataSource: UICollectionViewDiffableDataSource? - private let layoutFactory: PopupSearchLayoutFactory = PopupSearchLayoutFactory() + private var layoutFactory: PopupSearchLayoutFactory = PopupSearchLayoutFactory() let recentSearchTagRemoveButtonTapped = PublishRelay() let recentSearchTagRemoveAllButtonTapped = PublishRelay() From 30c6167af22f1030a58da22591a5b0407c8f2de3 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sat, 17 May 2025 01:12:39 +0900 Subject: [PATCH 12/20] =?UTF-8?q?refactor/#150:=20=EB=B0=98=EB=B3=B5?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=B6=94=EC=83=81=ED=99=94=20=EB=B0=8F=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EB=8D=94=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layout/CollectionLayoutProvidable.swift | 5 + .../Layout/GridCollectionLayoutProvider.swift | 32 +++++ .../Layout/HeaderLayoutProvidable.swift | 5 + .../Layout/TagCollectionLayoutProvider.swift | 51 +++++++ .../Factory/PopupSearchLayoutFactory.swift | 128 +++++------------- .../PopupSearch/View/PopupSearchView.swift | 9 +- 6 files changed, 131 insertions(+), 99 deletions(-) create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift new file mode 100644 index 00000000..d7c3efec --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift @@ -0,0 +1,5 @@ +import UIKit + +public protocol CollectionLayoutProvidable { + func makeLayout() -> NSCollectionLayoutSection +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift new file mode 100644 index 00000000..bfd54a1f --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift @@ -0,0 +1,32 @@ +import UIKit + +public struct GridCollectionLayoutProvider: CollectionLayoutProvidable { + public init() { } + + public func makeLayout() -> NSCollectionLayoutSection { + // Item + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(0.5), + heightDimension: .absolute(249) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + // Group + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(249) + ) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item, item] + ) + group.interItemSpacing = .fixed(16) + + // Section + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20) + section.interGroupSpacing = 24 + + return section + } +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift new file mode 100644 index 00000000..f700a126 --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift @@ -0,0 +1,5 @@ +import UIKit + +public protocol HeaderLayoutProvidable { + func makeHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift new file mode 100644 index 00000000..0f6fb1d5 --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift @@ -0,0 +1,51 @@ +import UIKit + +public struct TagCollectionLayoutProvider: CollectionLayoutProvidable, HeaderLayoutProvidable { + public init() { } + + public func makeLayout() -> NSCollectionLayoutSection { + // Item + let itemSize = NSCollectionLayoutSize( + widthDimension: .estimated(100), + heightDimension: .absolute(31) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + // Group + let groupSize = NSCollectionLayoutSize( + widthDimension: .estimated(100), + heightDimension: .estimated(31) + ) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item] + ) + + // Section + let section = NSCollectionLayoutSection(group: group) + section.orthogonalScrollingBehavior = .continuous + section.interGroupSpacing = 6 + + return section + } + + public func makeHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem { + let headerSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(24) + ) + return NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: headerSize, + elementKind: elementKind, + alignment: .top + ) + } + + public func configureSectionInsets(_ section: NSCollectionLayoutSection, isRecentSearch: Bool) { + if isRecentSearch { + section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 48, trailing: 20) + } else { + section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20) + } + } +} diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index b79662f1..82b96ddf 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -1,74 +1,51 @@ import UIKit +import DesignSystem // MARK: - Layout - - func makeCollectionViewLayout( - dataSourceProvider: @escaping () -> UICollectionViewDiffableDataSource? - ) -> UICollectionViewLayout { - return UICollectionViewCompositionalLayout(sectionProvider: { [weak self] sectionIndex, _ -> NSCollectionLayoutSection? in - guard let self = self, - let dataSource = dataSourceProvider() else { return nil } struct PopupSearchLayoutFactory { + private let tagLayoutProvider = TagCollectionLayoutProvider() + private let gridLayoutProvider = GridCollectionLayoutProvider() + + private var sectionProvider: ((Int) -> PopupSearchSection?)? + + mutating func setSectionProvider(_ provider: @escaping (Int) -> PopupSearchSection?) { + self.sectionProvider = provider + } - // sectionIndex를 사용하여 현재 dataSource에서 Section 타입을 가져옴 - guard sectionIndex < dataSource.snapshot().numberOfSections, - let sectionType = dataSource.sectionIdentifier(for: sectionIndex) else { return nil } + func makeCollectionViewLayout() -> UICollectionViewLayout { + return UICollectionViewCompositionalLayout { sectionIndex, environment -> NSCollectionLayoutSection? in + guard let sectionType = sectionProvider?(sectionIndex) else { return nil } switch sectionType { case .recentSearch: - return makeTagSectionLayout(PopupSearchView.SectionHeaderKind.recentSearch.rawValue) - + let layout = self.tagLayoutProvider.makeLayout() + self.tagLayoutProvider.configureSectionInsets(layout, isRecentSearch: true) + layout.boundarySupplementaryItems = [ + self.tagLayoutProvider.makeHeaderLayout(PopupSearchView.SectionHeaderKind.recentSearch.rawValue) + ] + return layout + case .category: - return makeTagSectionLayout(PopupSearchView.SectionHeaderKind.category.rawValue) - + let layout = self.tagLayoutProvider.makeLayout() + self.tagLayoutProvider.configureSectionInsets(layout, isRecentSearch: false) + layout.boundarySupplementaryItems = [ + self.tagLayoutProvider.makeHeaderLayout(PopupSearchView.SectionHeaderKind.category.rawValue) + ] + return layout + case .searchResultHeader: return makeSearchResultHeaderSectionLayout() - + case .searchResult: - return makeSearchResultSectionLayout() - + return self.gridLayoutProvider.makeLayout() + case .searchResultEmpty: return makeSearchResultEmptySectionLayout() } - }) - } - - func makeTagSectionLayout(_ headerKind: String) -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .estimated(100), - heightDimension: .absolute(31) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .estimated(100), - heightDimension: .estimated(31) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item] - ) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.orthogonalScrollingBehavior = .continuous - - if headerKind == PopupSearchView.SectionHeaderKind.recentSearch.rawValue { - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 48, trailing: 20) - } else { - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20) } - - section.interGroupSpacing = 6 - - section.boundarySupplementaryItems = [makeTagCollectionHeaderLayout(headerKind)] - - return section } - - func makeSearchResultHeaderSectionLayout() -> NSCollectionLayoutSection { + + private func makeSearchResultHeaderSectionLayout() -> NSCollectionLayoutSection { // Item let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), @@ -93,35 +70,7 @@ struct PopupSearchLayoutFactory { return section } - func makeSearchResultSectionLayout() -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(0.5), - heightDimension: .absolute(249) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(249) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item, item] - ) - group.interItemSpacing = .fixed(16) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20) - section.interGroupSpacing = 24 - - return section - } - - func makeSearchResultEmptySectionLayout() -> NSCollectionLayoutSection { - + private func makeSearchResultEmptySectionLayout() -> NSCollectionLayoutSection { // Item let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), @@ -146,17 +95,4 @@ struct PopupSearchLayoutFactory { return section } - - func makeTagCollectionHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem { - // Header - let headerSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(24) - ) - return NSCollectionLayoutBoundarySupplementaryItem( - layoutSize: headerSize, - elementKind: elementKind, - alignment: .top - ) - } } diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift index 9ea87db4..da8d3b02 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift @@ -27,9 +27,12 @@ final class PopupSearchView: UIView { public let searchBar = PPSearchBarView() lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()).then { - let layout = layoutFactory.makeCollectionViewLayout { [weak self] in self?.dataSource } - - $0.setCollectionViewLayout(layout, animated: false) + layoutFactory.setSectionProvider { [weak self] index in + guard let self, let dataSource else { return nil } + return dataSource.sectionIdentifier(for: index) + } + + $0.setCollectionViewLayout(layoutFactory.makeCollectionViewLayout(), animated: false) $0.register( TagCollectionHeaderView.self, From 5505d8e4858b9367dd30a329d903ba877a61923c Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sat, 17 May 2025 01:12:39 +0900 Subject: [PATCH 13/20] =?UTF-8?q?refactor/#150:=20=EB=B0=98=EB=B3=B5?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=B6=94=EC=83=81=ED=99=94=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EA=B2=B0=ED=95=A9=EB=8F=84=20=EC=A4=84?= =?UTF-8?q?=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layout/CollectionLayoutProvidable.swift | 5 + .../Layout/GridCollectionLayoutProvider.swift | 32 +++++ .../Layout/HeaderLayoutProvidable.swift | 5 + .../Layout/TagCollectionLayoutProvider.swift | 51 +++++++ .../Factory/PopupSearchLayoutFactory.swift | 128 +++++------------- .../PopupSearch/View/PopupSearchView.swift | 9 +- 6 files changed, 131 insertions(+), 99 deletions(-) create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift new file mode 100644 index 00000000..d7c3efec --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift @@ -0,0 +1,5 @@ +import UIKit + +public protocol CollectionLayoutProvidable { + func makeLayout() -> NSCollectionLayoutSection +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift new file mode 100644 index 00000000..bfd54a1f --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift @@ -0,0 +1,32 @@ +import UIKit + +public struct GridCollectionLayoutProvider: CollectionLayoutProvidable { + public init() { } + + public func makeLayout() -> NSCollectionLayoutSection { + // Item + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(0.5), + heightDimension: .absolute(249) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + // Group + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(249) + ) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item, item] + ) + group.interItemSpacing = .fixed(16) + + // Section + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20) + section.interGroupSpacing = 24 + + return section + } +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift new file mode 100644 index 00000000..f700a126 --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift @@ -0,0 +1,5 @@ +import UIKit + +public protocol HeaderLayoutProvidable { + func makeHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift new file mode 100644 index 00000000..0f6fb1d5 --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift @@ -0,0 +1,51 @@ +import UIKit + +public struct TagCollectionLayoutProvider: CollectionLayoutProvidable, HeaderLayoutProvidable { + public init() { } + + public func makeLayout() -> NSCollectionLayoutSection { + // Item + let itemSize = NSCollectionLayoutSize( + widthDimension: .estimated(100), + heightDimension: .absolute(31) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + // Group + let groupSize = NSCollectionLayoutSize( + widthDimension: .estimated(100), + heightDimension: .estimated(31) + ) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item] + ) + + // Section + let section = NSCollectionLayoutSection(group: group) + section.orthogonalScrollingBehavior = .continuous + section.interGroupSpacing = 6 + + return section + } + + public func makeHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem { + let headerSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(24) + ) + return NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: headerSize, + elementKind: elementKind, + alignment: .top + ) + } + + public func configureSectionInsets(_ section: NSCollectionLayoutSection, isRecentSearch: Bool) { + if isRecentSearch { + section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 48, trailing: 20) + } else { + section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20) + } + } +} diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index b79662f1..82b96ddf 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -1,74 +1,51 @@ import UIKit +import DesignSystem // MARK: - Layout - - func makeCollectionViewLayout( - dataSourceProvider: @escaping () -> UICollectionViewDiffableDataSource? - ) -> UICollectionViewLayout { - return UICollectionViewCompositionalLayout(sectionProvider: { [weak self] sectionIndex, _ -> NSCollectionLayoutSection? in - guard let self = self, - let dataSource = dataSourceProvider() else { return nil } struct PopupSearchLayoutFactory { + private let tagLayoutProvider = TagCollectionLayoutProvider() + private let gridLayoutProvider = GridCollectionLayoutProvider() + + private var sectionProvider: ((Int) -> PopupSearchSection?)? + + mutating func setSectionProvider(_ provider: @escaping (Int) -> PopupSearchSection?) { + self.sectionProvider = provider + } - // sectionIndex를 사용하여 현재 dataSource에서 Section 타입을 가져옴 - guard sectionIndex < dataSource.snapshot().numberOfSections, - let sectionType = dataSource.sectionIdentifier(for: sectionIndex) else { return nil } + func makeCollectionViewLayout() -> UICollectionViewLayout { + return UICollectionViewCompositionalLayout { sectionIndex, environment -> NSCollectionLayoutSection? in + guard let sectionType = sectionProvider?(sectionIndex) else { return nil } switch sectionType { case .recentSearch: - return makeTagSectionLayout(PopupSearchView.SectionHeaderKind.recentSearch.rawValue) - + let layout = self.tagLayoutProvider.makeLayout() + self.tagLayoutProvider.configureSectionInsets(layout, isRecentSearch: true) + layout.boundarySupplementaryItems = [ + self.tagLayoutProvider.makeHeaderLayout(PopupSearchView.SectionHeaderKind.recentSearch.rawValue) + ] + return layout + case .category: - return makeTagSectionLayout(PopupSearchView.SectionHeaderKind.category.rawValue) - + let layout = self.tagLayoutProvider.makeLayout() + self.tagLayoutProvider.configureSectionInsets(layout, isRecentSearch: false) + layout.boundarySupplementaryItems = [ + self.tagLayoutProvider.makeHeaderLayout(PopupSearchView.SectionHeaderKind.category.rawValue) + ] + return layout + case .searchResultHeader: return makeSearchResultHeaderSectionLayout() - + case .searchResult: - return makeSearchResultSectionLayout() - + return self.gridLayoutProvider.makeLayout() + case .searchResultEmpty: return makeSearchResultEmptySectionLayout() } - }) - } - - func makeTagSectionLayout(_ headerKind: String) -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .estimated(100), - heightDimension: .absolute(31) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .estimated(100), - heightDimension: .estimated(31) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item] - ) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.orthogonalScrollingBehavior = .continuous - - if headerKind == PopupSearchView.SectionHeaderKind.recentSearch.rawValue { - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 48, trailing: 20) - } else { - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20) } - - section.interGroupSpacing = 6 - - section.boundarySupplementaryItems = [makeTagCollectionHeaderLayout(headerKind)] - - return section } - - func makeSearchResultHeaderSectionLayout() -> NSCollectionLayoutSection { + + private func makeSearchResultHeaderSectionLayout() -> NSCollectionLayoutSection { // Item let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), @@ -93,35 +70,7 @@ struct PopupSearchLayoutFactory { return section } - func makeSearchResultSectionLayout() -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(0.5), - heightDimension: .absolute(249) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(249) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item, item] - ) - group.interItemSpacing = .fixed(16) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20) - section.interGroupSpacing = 24 - - return section - } - - func makeSearchResultEmptySectionLayout() -> NSCollectionLayoutSection { - + private func makeSearchResultEmptySectionLayout() -> NSCollectionLayoutSection { // Item let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), @@ -146,17 +95,4 @@ struct PopupSearchLayoutFactory { return section } - - func makeTagCollectionHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem { - // Header - let headerSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(24) - ) - return NSCollectionLayoutBoundarySupplementaryItem( - layoutSize: headerSize, - elementKind: elementKind, - alignment: .top - ) - } } diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift index 9ea87db4..da8d3b02 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift @@ -27,9 +27,12 @@ final class PopupSearchView: UIView { public let searchBar = PPSearchBarView() lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()).then { - let layout = layoutFactory.makeCollectionViewLayout { [weak self] in self?.dataSource } - - $0.setCollectionViewLayout(layout, animated: false) + layoutFactory.setSectionProvider { [weak self] index in + guard let self, let dataSource else { return nil } + return dataSource.sectionIdentifier(for: index) + } + + $0.setCollectionViewLayout(layoutFactory.makeCollectionViewLayout(), animated: false) $0.register( TagCollectionHeaderView.self, From 4fde99d93a45f5eba2afbbb963ea48f07f314697 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sat, 17 May 2025 20:15:02 +0900 Subject: [PATCH 14/20] =?UTF-8?q?refactor/#150:=20TagCollection=20layout?= =?UTF-8?q?=20inset=20=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layout/TagCollectionLayoutProvider.swift | 8 -------- .../Factory/PopupSearchLayoutFactory.swift | 16 +++++++++++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift index 0f6fb1d5..fafe7d65 100644 --- a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift @@ -40,12 +40,4 @@ public struct TagCollectionLayoutProvider: CollectionLayoutProvidable, HeaderLay alignment: .top ) } - - public func configureSectionInsets(_ section: NSCollectionLayoutSection, isRecentSearch: Bool) { - if isRecentSearch { - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 48, trailing: 20) - } else { - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20) - } - } } diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index 82b96ddf..c0e786a9 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -13,23 +13,29 @@ struct PopupSearchLayoutFactory { } func makeCollectionViewLayout() -> UICollectionViewLayout { - return UICollectionViewCompositionalLayout { sectionIndex, environment -> NSCollectionLayoutSection? in + return UICollectionViewCompositionalLayout { + sectionIndex, + environment -> NSCollectionLayoutSection? in guard let sectionType = sectionProvider?(sectionIndex) else { return nil } switch sectionType { case .recentSearch: let layout = self.tagLayoutProvider.makeLayout() - self.tagLayoutProvider.configureSectionInsets(layout, isRecentSearch: true) + layout.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 48, trailing: 20) layout.boundarySupplementaryItems = [ - self.tagLayoutProvider.makeHeaderLayout(PopupSearchView.SectionHeaderKind.recentSearch.rawValue) + self.tagLayoutProvider.makeHeaderLayout( + PopupSearchView.SectionHeaderKind.recentSearch.rawValue + ) ] return layout case .category: let layout = self.tagLayoutProvider.makeLayout() - self.tagLayoutProvider.configureSectionInsets(layout, isRecentSearch: false) + layout.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20) layout.boundarySupplementaryItems = [ - self.tagLayoutProvider.makeHeaderLayout(PopupSearchView.SectionHeaderKind.category.rawValue) + self.tagLayoutProvider.makeHeaderLayout( + PopupSearchView.SectionHeaderKind.category.rawValue + ) ] return layout From 98ae966b4378dc31bd2cdb49886200d37afcbd0f Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sat, 17 May 2025 22:16:47 +0900 Subject: [PATCH 15/20] =?UTF-8?q?chore/#150:=20gitignore=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 --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4449f4b9..01fa263e 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,5 @@ fastlane/screenshots/**/*.png fastlane/test_output # Cursor -Poppool/buildServer.json +**/buildServer.json .vscode/* - From 08e997224cb430b08f3a689af2009b8e81e8cc1b Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 18 May 2025 22:45:16 +0900 Subject: [PATCH 16/20] =?UTF-8?q?feat/#150:=20CollectionLayoutBuilder=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layout/CollectionLayoutBuilder.swift | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift new file mode 100644 index 00000000..dc1b037a --- /dev/null +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift @@ -0,0 +1,185 @@ +import UIKit + +public final class CollectionLayoutBuilder { + private var itemSize: NSCollectionLayoutSize? + private var groupSize: NSCollectionLayoutSize? + private var numberOfItemsPerGroup: Int = 1 + private var interItemSpacing: NSCollectionLayoutSpacing? + private var section: NSCollectionLayoutSection? + private var headerItem: NSCollectionLayoutBoundarySupplementaryItem? + + public init() { } + + public init(section existingSection: NSCollectionLayoutSection) { + self.section = existingSection + } + + @discardableResult + public func item( + width: NSCollectionLayoutDimension, + height: NSCollectionLayoutDimension + ) -> Self { + itemSize = NSCollectionLayoutSize( + widthDimension: width, + heightDimension: height + ) + + return self + } + + @discardableResult + public func group( + width: NSCollectionLayoutDimension, + height: NSCollectionLayoutDimension + ) -> Self { + groupSize = NSCollectionLayoutSize( + widthDimension: width, + heightDimension: height + ) + + return self + } + + @discardableResult + public func numberOfItemsPerGroup(_ count: Int) -> Self { + numberOfItemsPerGroup = count + + return self + } + + @discardableResult + public func itemSpacing(_ spacing: CGFloat) -> Self { + interItemSpacing = .fixed(spacing) + + return self + } + + @discardableResult + public func withContentInsets( + top: CGFloat = 0, + leading: CGFloat = 0, + bottom: CGFloat = 0, + trailing: CGFloat = 0 + ) -> Self { + section?.contentInsets = NSDirectionalEdgeInsets( + top: top, + leading: leading, + bottom: bottom, + trailing: trailing + ) + + return self + } + + @discardableResult + public func composeSection(_ axis: UIAxis) -> Self { + guard let itemSize, let groupSize else { + fatalError("Item and Group must be set before creating section") + } + + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + var group: NSCollectionLayoutGroup! + + switch axis { + case .vertical: + group = NSCollectionLayoutGroup.vertical( + layoutSize: groupSize, + subitems: Array(repeating: item, count: numberOfItemsPerGroup) + ) + + case .horizontal: + group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: Array(repeating: item, count: numberOfItemsPerGroup) + ) + + default: fatalError("Can't compose section to selected axis") + } + + if let interItemSpacing { + group.interItemSpacing = interItemSpacing + } + + section = NSCollectionLayoutSection(group: group) + + return self + } + + @discardableResult + public func header( + elementKind: String, + width: NSCollectionLayoutDimension = .fractionalWidth(1.0), + height: NSCollectionLayoutDimension = .fractionalHeight(1.0), + alignment: NSRectAlignment = .top + ) -> Self { + let headerSize = NSCollectionLayoutSize( + widthDimension: width, + heightDimension: height + ) + + headerItem = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: headerSize, + elementKind: elementKind, + alignment: alignment + ) + + if let headerItem { + section?.boundarySupplementaryItems = [headerItem] + } + + return self + } + + @discardableResult + public func withScrollingBehavior(_ behavior: UICollectionLayoutSectionOrthogonalScrollingBehavior) -> Self { + section?.orthogonalScrollingBehavior = behavior + + return self + } + + @discardableResult + public func groupSpacing(_ spacing: CGFloat) -> Self { + section?.interGroupSpacing = spacing + + return self + } + + @discardableResult + public func modifySection(_ modifier: (NSCollectionLayoutSection) -> Void) -> Self { + if let section = self.section { + modifier(section) + } + return self + } + + @discardableResult + public func withExistingHeader(_ headerItem: NSCollectionLayoutBoundarySupplementaryItem) -> Self { + self.headerItem = headerItem + + if let section = self.section { + section.boundarySupplementaryItems = [headerItem] + } + + return self + } + + @discardableResult + public func header(_ headerItems: [NSCollectionLayoutBoundarySupplementaryItem]) -> Self { + if let section = self.section { + section.boundarySupplementaryItems = headerItems + } + + return self + } + + public func build() -> NSCollectionLayoutSection { + guard let section else { fatalError("Section must be created before building") } + return section + } + + public func buildHeader() -> NSCollectionLayoutBoundarySupplementaryItem { + guard let headerItem else { fatalError("Header must be created before building") } + return headerItem + } +} From 47f5066b5936ed2a0554a4209d598deed1f70d4d Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 18 May 2025 22:46:51 +0900 Subject: [PATCH 17/20] =?UTF-8?q?refactor/#150:=20LayoutFactory=EC=97=90?= =?UTF-8?q?=20=EB=B9=8C=EB=8D=94=20=ED=8C=A8=ED=84=B4=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layout/GridCollectionLayoutProvider.swift | 35 +++++----------- .../Layout/TagCollectionLayoutProvider.swift | 42 +++++-------------- 2 files changed, 20 insertions(+), 57 deletions(-) diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift index bfd54a1f..a712d7cc 100644 --- a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift @@ -4,29 +4,14 @@ public struct GridCollectionLayoutProvider: CollectionLayoutProvidable { public init() { } public func makeLayout() -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(0.5), - heightDimension: .absolute(249) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(249) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item, item] - ) - group.interItemSpacing = .fixed(16) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20) - section.interGroupSpacing = 24 - - return section + return CollectionLayoutBuilder() + .item(width: .fractionalWidth(0.5), height: .absolute(249)) + .group(width: .fractionalWidth(1.0), height: .absolute(249)) + .numberOfItemsPerGroup(2) + .itemSpacing(16) + .composeSection(.horizontal) + .withContentInsets(top: 16, leading: 20, bottom: 0, trailing: 20) + .groupSpacing(24) + .build() } -} +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift index fafe7d65..24f34a57 100644 --- a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift @@ -4,40 +4,18 @@ public struct TagCollectionLayoutProvider: CollectionLayoutProvidable, HeaderLay public init() { } public func makeLayout() -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .estimated(100), - heightDimension: .absolute(31) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .estimated(100), - heightDimension: .estimated(31) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item] - ) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.orthogonalScrollingBehavior = .continuous - section.interGroupSpacing = 6 - - return section + return CollectionLayoutBuilder() + .item(width: .estimated(100), height: .absolute(31)) + .group(width: .estimated(100), height: .estimated(31)) + .composeSection(.vertical) + .withScrollingBehavior(.continuous) + .groupSpacing(6) + .build() } public func makeHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem { - let headerSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .absolute(24) - ) - return NSCollectionLayoutBoundarySupplementaryItem( - layoutSize: headerSize, - elementKind: elementKind, - alignment: .top - ) + return CollectionLayoutBuilder() + .header(elementKind: elementKind, height: .absolute(24)) + .buildHeader() } } From 2580238a367fb814bd43c6b30e10d79af625139e Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Sun, 18 May 2025 22:48:01 +0900 Subject: [PATCH 18/20] =?UTF-8?q?refactor/#150:=20PopupSearch=EC=9D=98=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EC=97=90=20=EB=B9=8C=EB=8D=94=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Factory/PopupSearchLayoutFactory.swift | 116 +++++++----------- 1 file changed, 45 insertions(+), 71 deletions(-) diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift index c0e786a9..e134a780 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift @@ -1,104 +1,78 @@ import UIKit + import DesignSystem // MARK: - Layout struct PopupSearchLayoutFactory { private let tagLayoutProvider = TagCollectionLayoutProvider() private let gridLayoutProvider = GridCollectionLayoutProvider() - + private var sectionProvider: ((Int) -> PopupSearchSection?)? - + mutating func setSectionProvider(_ provider: @escaping (Int) -> PopupSearchSection?) { self.sectionProvider = provider } func makeCollectionViewLayout() -> UICollectionViewLayout { - return UICollectionViewCompositionalLayout { - sectionIndex, - environment -> NSCollectionLayoutSection? in + return UICollectionViewCompositionalLayout { (sectionIndex, _) -> NSCollectionLayoutSection? in + guard let sectionType = sectionProvider?(sectionIndex) else { return nil } switch sectionType { case .recentSearch: - let layout = self.tagLayoutProvider.makeLayout() - layout.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 48, trailing: 20) - layout.boundarySupplementaryItems = [ - self.tagLayoutProvider.makeHeaderLayout( - PopupSearchView.SectionHeaderKind.recentSearch.rawValue - ) - ] - return layout - + return makeRecentSearchSectionLayout() + case .category: - let layout = self.tagLayoutProvider.makeLayout() - layout.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20) - layout.boundarySupplementaryItems = [ - self.tagLayoutProvider.makeHeaderLayout( - PopupSearchView.SectionHeaderKind.category.rawValue - ) - ] - return layout - + return makeCategorySectionLayout() + case .searchResultHeader: return makeSearchResultHeaderSectionLayout() - + case .searchResult: return self.gridLayoutProvider.makeLayout() - + case .searchResultEmpty: return makeSearchResultEmptySectionLayout() } } } - + + private func makeRecentSearchSectionLayout() -> NSCollectionLayoutSection { + + return CollectionLayoutBuilder(section: tagLayoutProvider.makeLayout()) + .withContentInsets(top: 16, leading: 20, bottom: 48, trailing: 20) + .header([self.tagLayoutProvider.makeHeaderLayout( + PopupSearchView.SectionHeaderKind.recentSearch.rawValue + )]) + .build() + } + + private func makeCategorySectionLayout() -> NSCollectionLayoutSection { + + return CollectionLayoutBuilder(section: tagLayoutProvider.makeLayout()) + .withContentInsets(top: 16, leading: 20, bottom: 16, trailing: 20) + .header([ + tagLayoutProvider.makeHeaderLayout(PopupSearchView.SectionHeaderKind.category.rawValue) + ]) + .build() + } + private func makeSearchResultHeaderSectionLayout() -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .estimated(22) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .estimated(22) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item] - ) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20) - - return section + + return CollectionLayoutBuilder() + .item(width: .fractionalWidth(1.0), height: .estimated(22)) + .group(width: .fractionalWidth(1.0), height: .estimated(22)) + .composeSection(.horizontal) + .withContentInsets(top: 0, leading: 20, bottom: 0, trailing: 20) + .build() } private func makeSearchResultEmptySectionLayout() -> NSCollectionLayoutSection { - // Item - let itemSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .fractionalHeight(1.0) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - // Group - let groupSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1.0), - heightDimension: .fractionalHeight(1.0) - ) - - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item] - ) - - // Section - let section = NSCollectionLayoutSection(group: group) - section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 0, trailing: 20) - - return section + + return CollectionLayoutBuilder() + .item(width: .fractionalWidth(1.0), height: .fractionalHeight(1.0)) + .group(width: .fractionalWidth(1.0), height: .fractionalHeight(1.0)) + .composeSection(.vertical) + .build() } } From 65d74c89572194c22f46270e4b70fc1e3b2d5f5e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 18 May 2025 13:57:12 +0000 Subject: [PATCH 19/20] style/#150: Apply SwiftLint autocorrect --- .../Extension/Collection+.swift | 2 +- .../Layout/CollectionLayoutBuilder.swift | 46 +++++++++---------- .../Layout/GridCollectionLayoutProvider.swift | 2 +- .../Layout/HeaderLayoutProvidable.swift | 2 +- .../Layout/TagCollectionLayoutProvider.swift | 6 +-- .../Reactor/PopupSearchReactor.swift | 5 +- .../PopupSearch/View/PopupSearchView.swift | 2 +- 7 files changed, 31 insertions(+), 34 deletions(-) diff --git a/Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift b/Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift index a4b22fc3..f2c58874 100644 --- a/Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift +++ b/Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift @@ -4,4 +4,4 @@ public extension Collection { subscript(safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } -} +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift index dc1b037a..82c9a901 100644 --- a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift @@ -7,7 +7,7 @@ public final class CollectionLayoutBuilder { private var interItemSpacing: NSCollectionLayoutSpacing? private var section: NSCollectionLayoutSection? private var headerItem: NSCollectionLayoutBoundarySupplementaryItem? - + public init() { } public init(section existingSection: NSCollectionLayoutSection) { @@ -26,7 +26,7 @@ public final class CollectionLayoutBuilder { return self } - + @discardableResult public func group( width: NSCollectionLayoutDimension, @@ -39,21 +39,21 @@ public final class CollectionLayoutBuilder { return self } - + @discardableResult public func numberOfItemsPerGroup(_ count: Int) -> Self { numberOfItemsPerGroup = count return self } - + @discardableResult public func itemSpacing(_ spacing: CGFloat) -> Self { interItemSpacing = .fixed(spacing) return self } - + @discardableResult public func withContentInsets( top: CGFloat = 0, @@ -70,13 +70,13 @@ public final class CollectionLayoutBuilder { return self } - + @discardableResult public func composeSection(_ axis: UIAxis) -> Self { guard let itemSize, let groupSize else { fatalError("Item and Group must be set before creating section") } - + let item = NSCollectionLayoutItem(layoutSize: itemSize) var group: NSCollectionLayoutGroup! @@ -96,16 +96,16 @@ public final class CollectionLayoutBuilder { default: fatalError("Can't compose section to selected axis") } - + if let interItemSpacing { group.interItemSpacing = interItemSpacing } - + section = NSCollectionLayoutSection(group: group) return self } - + @discardableResult public func header( elementKind: String, @@ -117,34 +117,34 @@ public final class CollectionLayoutBuilder { widthDimension: width, heightDimension: height ) - + headerItem = NSCollectionLayoutBoundarySupplementaryItem( layoutSize: headerSize, elementKind: elementKind, alignment: alignment ) - + if let headerItem { section?.boundarySupplementaryItems = [headerItem] } - + return self } - + @discardableResult public func withScrollingBehavior(_ behavior: UICollectionLayoutSectionOrthogonalScrollingBehavior) -> Self { section?.orthogonalScrollingBehavior = behavior return self } - + @discardableResult public func groupSpacing(_ spacing: CGFloat) -> Self { section?.interGroupSpacing = spacing return self } - + @discardableResult public func modifySection(_ modifier: (NSCollectionLayoutSection) -> Void) -> Self { if let section = self.section { @@ -152,15 +152,15 @@ public final class CollectionLayoutBuilder { } return self } - + @discardableResult public func withExistingHeader(_ headerItem: NSCollectionLayoutBoundarySupplementaryItem) -> Self { self.headerItem = headerItem - + if let section = self.section { section.boundarySupplementaryItems = [headerItem] } - + return self } @@ -169,17 +169,17 @@ public final class CollectionLayoutBuilder { if let section = self.section { section.boundarySupplementaryItems = headerItems } - + return self } - + public func build() -> NSCollectionLayoutSection { guard let section else { fatalError("Section must be created before building") } return section } - + public func buildHeader() -> NSCollectionLayoutBoundarySupplementaryItem { guard let headerItem else { fatalError("Header must be created before building") } return headerItem } -} +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift index a712d7cc..15322073 100644 --- a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift @@ -2,7 +2,7 @@ import UIKit public struct GridCollectionLayoutProvider: CollectionLayoutProvidable { public init() { } - + public func makeLayout() -> NSCollectionLayoutSection { return CollectionLayoutBuilder() .item(width: .fractionalWidth(0.5), height: .absolute(249)) diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift index f700a126..ad2e25fe 100644 --- a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift @@ -2,4 +2,4 @@ import UIKit public protocol HeaderLayoutProvidable { func makeHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem -} +} diff --git a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift index 24f34a57..47fc72c1 100644 --- a/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift +++ b/Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift @@ -2,7 +2,7 @@ import UIKit public struct TagCollectionLayoutProvider: CollectionLayoutProvidable, HeaderLayoutProvidable { public init() { } - + public func makeLayout() -> NSCollectionLayoutSection { return CollectionLayoutBuilder() .item(width: .estimated(100), height: .absolute(31)) @@ -12,10 +12,10 @@ public struct TagCollectionLayoutProvider: CollectionLayoutProvidable, HeaderLay .groupSpacing(6) .build() } - + public func makeHeaderLayout(_ elementKind: String) -> NSCollectionLayoutBoundarySupplementaryItem { return CollectionLayoutBuilder() .header(elementKind: elementKind, height: .absolute(24)) .buildHeader() } -} +} diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift index fde9cc33..2c931308 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift @@ -295,9 +295,7 @@ private extension PopupSearchReactor { } func makeSearchResultEmpty(state: State) -> String? { - if !currentState.searchResultItems.isEmpty { return nil } - else if currentState.isSearching { return "검색 결과가 없어요 :(\n다른 키워드로 검색해주세요" } - else { return "검색 결과가 없어요 :(\n다른 옵션을 선택해주세요" } + if !currentState.searchResultItems.isEmpty { return nil } else if currentState.isSearching { return "검색 결과가 없어요 :(\n다른 키워드로 검색해주세요" } else { return "검색 결과가 없어요 :(\n다른 옵션을 선택해주세요" } } /// 받침에 따라 이/가 를 판단해서 붙여준다. @@ -346,7 +344,6 @@ private extension PopupSearchReactor { } } - // MARK: - Mutate Handlers private extension PopupSearchReactor { func handleViewDidLoad() -> Observable { diff --git a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift index da8d3b02..bcd5b3e8 100644 --- a/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift +++ b/Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift @@ -31,7 +31,7 @@ final class PopupSearchView: UIView { guard let self, let dataSource else { return nil } return dataSource.sectionIdentifier(for: index) } - + $0.setCollectionViewLayout(layoutFactory.makeCollectionViewLayout(), animated: false) $0.register( From db403e373d3f9eab865b55930d560526eb5915e4 Mon Sep 17 00:00:00 2001 From: 0Hooni Date: Mon, 19 May 2025 12:18:59 +0900 Subject: [PATCH 20/20] =?UTF-8?q?chore/#150:=20=EC=BD=94=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EB=B9=97=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .coderabbit.yaml | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..85d62c3b --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,93 @@ +language: ko-KR # 언어 설정 + +early_access: true # 미리보기 기능 활성화 +enable_free_tier: true # 프리 티어 활성화 +auto_resolve_threads: false # 자동 해결 비활성화 + +reviews: + profile: chill + request_changes_workflow: true + high_level_summary: true # 리뷰에 대해 요약(high-level summary)를 자동 작성 + high_level_summary_placeholder: '@coderabbitai 요약' + auto_title_placeholder: '@coderabbitai' + poem: true + review_status: true # PR 리뷰 상태를 리뷰 요약란에 표시 + collapse_walkthrough: false # 리뷰 단계 설명을 기본적으로 접지 않음 + + abort_on_close: true # PR이 닫히면 리뷰 수행을 중단(abort) + + + auto_review: + enabled: true # 자동 리뷰 기능을 활성화 + auto_incremental_review: true # 커밋이 추가될 때마다 변경 사항에 대해서만 자동 수행 + ignore_title_keywords: [] # PR 제목에 포함되면 리뷰를 건너뛰는 키워드 목록 + labels: [] # 특정 라벨이 붙은 PR만 자동 리뷰 대상 + drafts: false # Draft 상태인 PR은 자동 리뷰 대상에서 제외(false면 제외) + base_branches: [] # 특정 브랜치만 리뷰하도록 + + tools: + shellcheck: # 셸 스크립트 문법 및 보안 검사 + enabled: true + ruff: # Python 코드 스타일 검사기 + enabled: true + markdownlint: # 마크다운 문법 검사 + enabled: true + github-checks: # GitHub 체크 연동 + 타임아웃(ms 단위) + enabled: true + timeout_ms: 90000 + languagetool: # 맞춤법, 문법 검사 + enabled: true + disabled_rules: + - EN_UNPAIRED_BRACKETS + - EN_UNPAIRED_QUOTES + disabled_categories: + - TYPOS + - TYPOGRAPHY + - CASING + enabled_only: false + level: default + enabled_rules: [] + enabled_categories: [] + biome: # JavaScript/TypeScript 정적 분석 + enabled: true + hadolint: # Dockerfile 코드 스타일 검사 + enabled: true + swiftlint: # Swift 코드 스타일 검사 + enabled: true + phpstan: # PHP 정적 분석 + enabled: true + level: default + golangci-lint: # Go 코드 스타일 검사 + enabled: true + yamllint: # YAML 형식 검사 + enabled: true + gitleaks: # Git 시크릿 노출 탐지 + enabled: true + checkov: # 인프라 보안 검사 + enabled: true + ast-grep: # AST 기반 코드 패턴 검사 + packages: [] + rule_dirs: [] + util_dirs: [] + essential_rules: true + +# CodeRabbit AI 챗 기능을 사용 가능하게 하고, +# 한 번에 처리 가능한 토큰 수를 최대 4096으로 제한 +chat: + enabled: true + max_token_length: 4096 + + +# 지식 기반에 사용할 학습 범위를 지정하십시오. +# 'Local' - Repository +# 'Global'- Organization +# 'Auto' - Repository(users public) + Organization(private) +knowledge_base: + web_search: # AI 웹 검색 허용 + enabled: true + learnings: # 학습 범위 설정 (local, global, auto) + scope: local + issues: # 이슈 자동 참조 범위 설정 (local, global, auto) + scope: auto + jira: + project_keys: []