Skip to content

Conversation

@0Hooni
Copy link
Member

@0Hooni 0Hooni commented May 18, 2025

📌 이슈

✅ 작업 사항

  • 팩토리 세분화
  • 재사용 가능한 팩토리로 수정
  • 레이아웃 빌더를 도입

🚀 테스트 방식

SearchFeature 데모를 실행하여 검색 화면이 이전과 동일한 레이아웃을 제공해주는지 테스트

👀 ETC (추후 개발해야 할 것, 참고자료 등)

새롭게 도입한 빌더 패턴은 기존의 Sectionable이 제공해주었던 CollectionLayout이 만들기 쉽지 않다는 문제점을 개선하기 위해 도입했던 부분에 충분히 공감하였기에 새롭게 제시하는 방식입니다.

현재 검색화면을 만드는데에는 충분히 커버를 해주고있지만, 팀원분들의 화면을 커버 가능한지 확인이 필요합니다.

Summary by CodeRabbit

  • New Features

    • 복잡한 컬렉션 뷰 레이아웃 구성을 위한 빌더 도입.
    • 표준화된 섹션 및 헤더 레이아웃을 위한 프로토콜과 레이아웃 제공자 추가.
    • 빈 검색 결과를 표시하는 전용 셀과 새로운 섹션 타입 추가.
  • Refactor

    • 검색 기능 내 섹션 처리를 단일 열거형으로 통합.
    • 레이아웃 생성과 변이 처리 로직을 모듈화 및 간소화.
    • 빈 검색 결과 셀 명명 일관성 개선.
  • Bug Fixes

    • 빈 검색 결과 섹션의 아이템 선택 시 의도치 않은 동작 방지.
  • Chores

    • 의존성 및 파일 관리를 위한 프로젝트 구성과 .gitignore 업데이트.

0Hooni added 20 commits May 15, 2025 19:01
…pPool/iOS into refactor/#150-layout-abstract

# Conflicts:
#	.gitignore
- SearchResult Section을 유동적으로 사용하던것을 분리함
- 이로 인해 dataSource의 의존성을 줄임
@0Hooni 0Hooni self-assigned this May 18, 2025
@0Hooni 0Hooni added 🔄 refactor 프로덕션 코드 리팩토링, 파일 삭제, 네이밍 수정 및 폴더링 🚀 feat 새로운 기능을 추가 labels May 18, 2025
@0Hooni 0Hooni linked an issue May 18, 2025 that may be closed by this pull request
@0Hooni 0Hooni marked this pull request as draft May 18, 2025 14:01
@0Hooni 0Hooni marked this pull request as ready for review May 19, 2025 03:10
@coderabbitai
Copy link

coderabbitai bot commented May 19, 2025

## Walkthrough

This update introduces a compositional layout builder and protocol-driven layout providers for collection views, refactors the search feature's layout factory and reactor to use these abstractions, and unifies section management with a new `PopupSearchSection` enum. It also updates cell and enum naming, improves modularity, and enhances snapshot and empty state handling.

## Changes

| File(s) | Change Summary |
|---------|---------------|
| `.gitignore` | Added rules to ignore `**/buildServer.json` files and all files in `.vscode/` directories. |
| `Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift` | Added a public extension to `Collection` with a safe subscript for bounds-checked access. |
| `Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift` | Introduced `CollectionLayoutBuilder` class for fluent compositional layout construction, with chainable methods for item/group configuration, headers, spacing, and insets. |
| `Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift` | Added `CollectionLayoutProvidable` protocol requiring a `makeLayout()` method returning a layout section. |
| `Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift` | Added `GridCollectionLayoutProvider` struct implementing `CollectionLayoutProvidable` for grid layouts. |
| `Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift` | Added `HeaderLayoutProvidable` protocol for providing header layout supplementary items. |
| `Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift` | Added `TagCollectionLayoutProvider` struct implementing both layout provider protocols for tag and header layouts. |
| `Poppool/PresentationLayer/SearchFeature/SearchFeature.xcodeproj/project.pbxproj` | Updated RxCocoa Swift package references and identifiers in the Xcode project file. |
| `Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift` | Refactored to a struct with injected section provider, delegated layout creation to provider structs and builder, removed direct data source dependency, and added a new section for empty search results. |
| `Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift` | Refactored mutation logic into handler methods, replaced empty title mutation with a section update mutation, and unified search result loading logic. |
| `Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift` | Introduced `PopupSearchSection` enum with cases for each UI section. |
| `Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyCollectionViewCell.swift` | Renamed class and references from `SearchResultEmptyTitleCollectionViewCell` to `SearchResultEmptyCollectionViewCell`. |
| `Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift` | Replaced internal section enum with `PopupSearchSection`, updated data source and snapshot logic, renamed empty item case, and refined layout factory usage. |
| `Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift` | Added handling for `.searchResultEmpty` section, updated state binding to use new pulse property, and simplified snapshot update logic. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant ViewController
    participant View
    participant LayoutFactory
    participant TagLayoutProvider
    participant GridLayoutProvider
    participant Builder

    ViewController->>View: Configure collectionView
    View->>LayoutFactory: Set sectionProvider
    View->>LayoutFactory: makeCollectionViewLayout()
    LayoutFactory->>LayoutFactory: sectionProvider(sectionIndex)
    alt recentSearch/category
        LayoutFactory->>TagLayoutProvider: makeLayout()/makeHeaderLayout()
        TagLayoutProvider->>Builder: configure layout
        Builder-->>TagLayoutProvider: NSCollectionLayoutSection/BoundarySupplementaryItem
        TagLayoutProvider-->>LayoutFactory: section/header
    else searchResult
        LayoutFactory->>GridLayoutProvider: makeLayout()
        GridLayoutProvider->>Builder: configure layout
        Builder-->>GridLayoutProvider: NSCollectionLayoutSection
        GridLayoutProvider-->>LayoutFactory: section
    else searchResultEmpty
        LayoutFactory->>Builder: configure empty layout
        Builder-->>LayoutFactory: NSCollectionLayoutSection
    end
    LayoutFactory-->>View: UICollectionViewLayout
sequenceDiagram
    participant Reactor
    participant Handler
    participant State

    Reactor->>Handler: mutate(action)
    Handler->>Handler: handleAction()
    Handler-->>Reactor: Observable<Mutation>
    Reactor->>State: reduce(state, mutation)
    State-->>Reactor: new state
Loading

Poem

In the warren of code where layouts bloom,
Builders and providers now banish the gloom.
Sections are named, with enum delight,
Empty results handled, all logic just right.
Snapshots refreshed, with spacing anew—
A rabbit’s proud hop for this search feature crew!
🐇✨


<!-- walkthrough_end -->

<!-- announcements_start -->

> [!NOTE]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
> Learn more [here](http://coderabbit.ai/ide).
> 
> </details>

---

> [!NOTE]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 65d74c89572194c22f46270e4b70fc1e3b2d5f5e and db403e373d3f9eab865b55930d560526eb5915e4.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `.coderabbit.yaml` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>.coderabbit.yaml (7)</summary>

`1-4`: **언어 및 액세스 설정 확인**

`language`, `early_access`, `enable_free_tier` 설정이 스키마와 일치하며 올바르게 구성되었습니다.

---

`5-6`: **자동 스레드 해결 설정 경로 검증 요청**

`auto_resolve_threads` 속성은 공식 스키마에서 정의된 위치가 보이지 않습니다. 이 설정이 올바른 경로에 배치되었는지(예: `reviews.auto_resolve_threads` 등) CodeRabbit 문서를 참고하여 검증해 주세요.

---

`7-18`: **리뷰 기본 동작 설정 검토**

`reviews.profile`, `reviews.request_changes_workflow`, `high_level_summary`, `high_level_summary_placeholder`, `auto_title_placeholder`, `poem`, `review_status`, `collapse_walkthrough`, `abort_on_close` 등이 스키마와 일치하며 적절하게 구성되었습니다.

---

`20-27`: **자동 리뷰(`auto_review`) 설정 확인**

`auto_review.enabled`, `auto_review.auto_incremental_review`, `ignore_title_keywords`, `labels`, `drafts`, `base_branches`가 스키마와 일치하며 기대한 동작을 수행합니다.

---

`28-73`: **도구(`tools`) 설정 검토**

`shellcheck`, `ruff`, `markdownlint`, `github-checks`, `languagetool`, `biome`, `hadolint`, `swiftlint`, `phpstan`, `golangci-lint`, `yamllint`, `gitleaks`, `checkov`, `ast-grep` 의 설정이 스키마와 일치합니다. 특히 `languagetool`의 `disabled_rules`/`disabled_categories` 구성도 적절합니다.

---

`74-79`: **`chat` 설정 스키마 일치 여부 확인 요청**

`chat.enabled` 및 `chat.max_token_length` 속성은 제공된 스키마에 정의되어 있지 않습니다. `chat.auto_reply` 등 올바른 속성명을 사용해야 할 수 있으므로 공식 문서를 참고하여 구성 확인을 권장드립니다.

---

`81-94`: **지식 기반(`knowledge_base`) 설정 적절**

`knowledge_base.web_search`, `knowledge_base.learnings.scope`, `knowledge_base.issues.scope`, `knowledge_base.jira.project_keys` 설정이 스키마와 일치하며 올바르게 구성되었습니다.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

<details open="true">
<summary>🧪 Generate Unit Tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with Unit Tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit Unit Tests in branch `refactor/#150-layout-abstract`
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post Copyable Unit Tests in Comment

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=PopPool/iOS&utm_content=151):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` or `@coderabbitai 요약` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (2)
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyCollectionViewCell.swift (2)

37-42: 🛠️ Refactor suggestion

Use contentView instead of self for subviews
By convention, subviews of a UICollectionViewCell should be added to contentView to ensure proper reuse and layout margins:

-    [emptyLabel].forEach {
-        self.addSubview($0)
-    }
+    [emptyLabel].forEach {
+        contentView.addSubview($0)
+    }

45-49: 🛠️ Refactor suggestion

Constrain within contentView
Adjust constraints to reference contentView instead of the cell itself to avoid layout issues when the cell’s bounds change:

-    emptyLabel.snp.makeConstraints { make in
-        make.top.equalToSuperview().inset(145)
-        make.horizontalEdges.equalToSuperview()
-        make.height.equalTo(42)
-    }
+    emptyLabel.snp.makeConstraints { make in
+        make.top.equalTo(contentView).inset(145)
+        make.horizontalEdges.equalTo(contentView)
+        make.height.equalTo(42)
+    }
🧹 Nitpick comments (10)
.gitignore (1)

45-47: Simplify and strengthen Git ignore rules

  • **/buildServer.json can be reduced to buildServer.json since Git matches files by name across all directories by default.
  • Use .vscode/ (with a trailing slash) to recursively ignore the entire .vscode directory rather than .vscode/*.
Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift (1)

3-7: Doc comment suggestion for maintainability
Consider adding a documentation comment above the subscript to explain its purpose and usage, for example:

/// Returns the element at `index` if it is within bounds, otherwise `nil`.
/// - Parameter index: The position of the element to retrieve.
/// - Returns: An optional element.
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift (1)

3-5: Documentation for public API
As this is a public protocol, consider adding a docstring to guide implementors, for example:

/// A type that can build and return a compositional layout section.
public protocol CollectionLayoutProvidable {  }
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyCollectionViewCell.swift (1)

55-59: Override prepareForReuse to reset content
Ensure that when cells are reused, the emptyLabel text is cleared:

override func prepareForReuse() {
    super.prepareForReuse()
    emptyLabel.text = nil
}

Adding this improves robustness under rapid scrolling or data changes.

Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift (1)

3-9: Add documentation for clarity
Consider adding a brief doc comment explaining each case’s UI role, which will help future contributors understand the enum’s purpose at a glance.

Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift (1)

3-5: Documentation for public protocol
Since this is a public protocol, adding a doc comment will improve discoverability and usage guidance:

/// Types conforming to this protocol can provide a boundary supplementary item (header) for collection layouts.
public protocol HeaderLayoutProvidable {  }
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift (1)

210-222: Consider improving variable naming for clarity.

The variable isEmpty is confusing because it's actually checking if updateSearchResultSection is nil, but the presence of a value in updateSearchResultSection indicates an empty search state (the opposite of what the name suggests).

-                let isEmpty = state.updateSearchResultSection == nil
-                let emptyCaseTitle = state.updateSearchResultSection
+                let hasEmptyState = state.updateSearchResultSection != nil
+                let emptyStateMessage = state.updateSearchResultSection
 
                 owner.mainView.updateSearchResultSectionSnapshot(
                     with: state.searchResultItems.map(PopupSearchView.SectionItem.searchResultItem),
                     header: PopupSearchView.SectionItem.searchResultHeaderItem(state.searchResultHeader),
-                    empty: isEmpty ? nil : PopupSearchView.SectionItem.searchResultEmptyItem(emptyCaseTitle!)
+                    empty: hasEmptyState ? PopupSearchView.SectionItem.searchResultEmptyItem(emptyStateMessage!) : nil
                 )
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift (2)

74-107: Consider simplifying the default case in the axis switch.

Since UIAxis only has .vertical and .horizontal cases, the default case seems unnecessary and could be simplified.

        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")
        }

176-184: Consider adding more descriptive error messages.

The fatal error messages could be more specific to help developers understand what went wrong during development.

    public func build() -> NSCollectionLayoutSection {
-        guard let section else { fatalError("Section must be created before building") }
+        guard let section else { fatalError("Section must be created using composeSection(_:) before calling build()") }
        return section
    }

    public func buildHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
-        guard let headerItem else { fatalError("Header must be created before building") }
+        guard let headerItem else { fatalError("Header must be created using header(...) method before calling buildHeader()") }
        return headerItem
    }
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift (1)

32-36: Avoid re-creating the grid layout on every call

self.gridLayoutProvider.makeLayout() is executed for every .searchResult section query.
If makeLayout() builds a full NSCollectionLayoutSection each time, this closure will allocate new layout objects repeatedly while scrolling.

Cache the section once, or let GridCollectionLayoutProvider expose an immutable section property.

private lazy var searchResultSection = gridLayoutProvider.makeLayout()
...
case .searchResult:
    return searchResultSection
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between a6ca1bf and 65d74c8.

📒 Files selected for processing (14)
  • .gitignore (1 hunks)
  • Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift (1 hunks)
  • Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift (1 hunks)
  • Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift (1 hunks)
  • Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift (1 hunks)
  • Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift (1 hunks)
  • Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift (1 hunks)
  • Poppool/PresentationLayer/SearchFeature/SearchFeature.xcodeproj/project.pbxproj (4 hunks)
  • Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift (1 hunks)
  • Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift (6 hunks)
  • Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift (1 hunks)
  • Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/Component/Cell/SearchResultEmptyCollectionViewCell.swift (3 hunks)
  • Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift (7 hunks)
  • Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift (2)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift (1)
  • makeLayout (6-16)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift (1)
  • makeLayout (6-14)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift (1)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift (1)
  • makeHeaderLayout (16-20)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift (2)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift (1)
  • makeLayout (6-16)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift (9)
  • item (17-28)
  • group (30-41)
  • composeSection (74-107)
  • withScrollingBehavior (134-139)
  • groupSpacing (141-146)
  • build (176-179)
  • header (109-132)
  • header (167-174)
  • buildHeader (181-184)
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift (1)
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift (1)
  • updateSearchResultSectionSnapshot (278-299)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift (2)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift (1)
  • makeLayout (6-14)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift (8)
  • item (17-28)
  • group (30-41)
  • numberOfItemsPerGroup (43-48)
  • itemSpacing (50-55)
  • composeSection (74-107)
  • withContentInsets (57-72)
  • groupSpacing (141-146)
  • build (176-179)
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift (2)
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift (2)
  • setSectionProvider (12-14)
  • makeCollectionViewLayout (16-38)
Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift (2)
  • header (109-132)
  • header (167-174)
🪛 SwiftLint (0.57.0)
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift

[Error] 216-216: Force casts should be avoided

(force_cast)

🔇 Additional comments (17)
Poppool/PresentationLayer/SearchFeature/SearchFeature.xcodeproj/project.pbxproj (4)

10-10: PBXBuildFile entry updated for RxCocoa
The new PBXBuildFile ID (0506BE882DD79A6C006CDEDE) correctly references the XCSwiftPackageProductDependency ID (0506BE872DD79A6C006CDEDE) for RxCocoa, ensuring the framework is now pulled in via Swift Package Manager.


181-181: Frameworks build phase reference updated
The PBXFrameworksBuildPhase now includes the updated RxCocoa build file ID (0506BE882DD79A6C006CDEDE), aligning with the new PBXBuildFile entry above.


314-314: SearchFeature target SPM dependency updated
The packageProductDependencies list for the SearchFeature target now correctly includes the new RxCocoa dependency ID (0506BE872DD79A6C006CDEDE), matching the Swift package product entry.


941-945: XCSwiftPackageProductDependency entry for RxCocoa
The XCSwiftPackageProductDependency block declares the RxCocoa product under the RxSwift package reference with the correct new ID. This completes the SPM integration for RxCocoa.

Poppool/CoreLayer/Infrastructure/Infrastructure/Extension/Collection+.swift (1)

3-7: Safety-first: Safe subscript implementation looks solid
The new subscript(safe:) correctly guards against out-of-bounds access by leveraging indices.contains(_:). This will prevent crashes when indexing. Consider adding unit tests for Arrays, Dictionaries, and Strings to exercise this extension.

Would you like me to scaffold a set of XCTest cases covering different Collection types?

Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutProvidable.swift (1)

3-5: Protocol design looks good
The CollectionLayoutProvidable protocol cleanly abstracts the creation of NSCollectionLayoutSection. It enables you to swap providers easily and aligns well with SOLID principles.

Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/SectionType/Section.swift (1)

3-9: Verify access level for cross-module use
The enum PopupSearchSection is currently internal. If you plan to use it from other modules (e.g., layout factories in the DesignSystem), consider marking it public. Otherwise, internal access is sufficient within the SearchFeature target.

Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/HeaderLayoutProvidable.swift (1)

3-5: Protocol abstraction is solid
The HeaderLayoutProvidable protocol cleanly separates header layout concerns, complementing CollectionLayoutProvidable. This adds modularity to your layout-building approach.

Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/GridCollectionLayoutProvider.swift (1)

3-17: LGTM! Well-structured grid layout provider implementation.

The implementation correctly uses the builder pattern to create a consistent grid layout with proper spacing and insets. The two-column grid with fixed item height and appropriate spacing will create a visually balanced layout.

Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchViewController.swift (1)

106-107: LGTM! Proper handling for empty search results section.

Correctly returning nil prevents actions when selecting items in the empty search result section, consistent with the existing pattern for the header section.

Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/TagCollectionLayoutProvider.swift (2)

3-14: LGTM! Clean implementation for tag collection layout.

The implementation properly configures a vertical layout with continuous scrolling behavior, which is ideal for displaying horizontally scrollable tags. The estimated width with fixed height accommodates variable text lengths.


16-20: LGTM! Well-implemented header layout provider.

The header implementation with a fixed height of 24 points is appropriate for section headers in a tag collection.

Poppool/PresentationLayer/DesignSystem/DesignSystem/Components/Layout/CollectionLayoutBuilder.swift (4)

3-16: LGTM! Well-structured builder class initialization.

The class effectively implements the builder pattern with appropriate property storage and initialization options. The option to initialize with an existing section provides good flexibility.


17-72: LGTM! Great implementation of chainable configuration methods.

The methods for configuring item sizes, group sizes, item count, spacing, and content insets are well-designed with proper return types for chaining and @discardableResult annotation.


109-146: LGTM! Well-implemented header configuration and scrolling behavior.

The methods for configuring headers and scrolling behavior are appropriately implemented with good default values and proper return types for method chaining.


148-174: LGTM! Good flexibility with section and header modification methods.

The custom modifiers for sections and headers provide additional flexibility for scenarios not covered by the standard builder methods.

Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Factory/PopupSearchLayoutFactory.swift (1)

10-17: sectionProvider being optional can silently break the layout at runtime

makeCollectionViewLayout() returns a compositional layout that assumes sectionProvider has been injected beforehand.
If a caller forgets to call setSectionProvider, every section will resolve to nil and nothing will be shown – no compile-time hint, no assert.

Consider making the dependency explicit by:

  1. Initialising the factory with a non-optional provider, or
  2. Throwing an assertion / precondition failure when the provider is missing.
- private var sectionProvider: ((Int) -> PopupSearchSection?)?
+ private let sectionProvider: (Int) -> PopupSearchSection?
 
- mutating func setSectionProvider(_ provider: @escaping (Int) -> PopupSearchSection?) {
-     self.sectionProvider = provider
- }
+ init(sectionProvider: @escaping (Int) -> PopupSearchSection?) {
+     self.sectionProvider = sectionProvider
+ }

This fails fast and prevents silent UI breakage.

Comment on lines +212 to 217
case .searchResultEmptyItem(let title):
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: SearchResultEmptyTitleCollectionViewCell.identifiers,
withReuseIdentifier: SearchResultEmptyCollectionViewCell.identifiers,
for: indexPath
) as! SearchResultEmptyTitleCollectionViewCell
) as! SearchResultEmptyCollectionViewCell

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace force-cast with safe down-cast to prevent runtime crashes

SwiftLint already flags this line.
If the cell class ever changes in Interface Builder or the identifier is mistyped, the app will crash at runtime.

- let cell = collectionView.dequeueReusableCell(
-     withReuseIdentifier: SearchResultEmptyCollectionViewCell.identifiers,
-     for: indexPath
- ) as! SearchResultEmptyCollectionViewCell
+ guard let cell = collectionView.dequeueReusableCell(
+     withReuseIdentifier: SearchResultEmptyCollectionViewCell.identifiers,
+     for: indexPath
+ ) as? SearchResultEmptyCollectionViewCell else {
+     assertionFailure("Failed to dequeue SearchResultEmptyCollectionViewCell")
+     return nil
+ }

Apply the same pattern to the other as! casts in this method to remove all force_cast warnings.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Error] 216-216: Force casts should be avoided

(force_cast)

🤖 Prompt for AI Agents
In
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/View/PopupSearchView.swift
around lines 212 to 217, replace the force-cast (as!) when dequeuing collection
view cells with a safe down-cast (as?) to prevent runtime crashes. Use optional
binding (if let or guard let) to safely unwrap the cell and handle the failure
case gracefully. Apply this pattern consistently to all force-casts in this
method to eliminate all force_cast warnings.

Comment on lines +297 to 299
func makeSearchResultEmpty(state: State) -> String? {
if !currentState.searchResultItems.isEmpty { return nil } else if currentState.isSearching { return "검색 결과가 없어요 :(\n다른 키워드로 검색해주세요" } else { return "검색 결과가 없어요 :(\n다른 옵션을 선택해주세요" }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Dead code – parameter state never used

Same issue as above: the parameter is never referenced, so Swift will emit a warning and the logic is misleading.
Once the fix above is applied, this discrepancy disappears.

🤖 Prompt for AI Agents
In
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift
around lines 297 to 299, the function makeSearchResultEmpty has an unused
parameter 'state' which causes a warning and confusion. Remove the 'state'
parameter from the function signature and update any calls to this function
accordingly to eliminate the dead code and clarify the logic.

Comment on lines +201 to 203
case .updateSearchResultSection:
newState.updateSearchResultSection = makeSearchResultEmpty(state: newState)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

updateSearchResultSection uses stale state – empty/keyword messages may be wrong

reduce sets

newState.updateSearchResultSection = makeSearchResultEmpty(state: newState)

However, makeSearchResultEmpty(state:) completely ignores its parameter and instead reads currentState, which still contains the old values.
Consequences:

  • After mutations like setupSearchResult the empty-state message is evaluated against the previous snapshot.
  • The UI can show an out-of-date empty/keyword message.

Fix by using the argument:

- func makeSearchResultEmpty(state: State) -> String? {
-     if !currentState.searchResultItems.isEmpty { return nil }
-     else if currentState.isSearching { … }
-     else { … }
- }
+ func makeSearchResultEmpty(state: State) -> String? {
+     if !state.searchResultItems.isEmpty { return nil }
+     else if state.isSearching {
+         return "검색 결과가 없어요 :(\n다른 키워드로 검색해주세요"
+     } else {
+         return "검색 결과가 없어요 :(\n다른 옵션을 선택해주세요"
+     }
+ }

Remove the now-unused state parameter from the call sites if you prefer to access state directly.

🤖 Prompt for AI Agents
In
Poppool/PresentationLayer/SearchFeature/SearchFeature/PopupSearch/Reactor/PopupSearchReactor.swift
around lines 201 to 203, the call to makeSearchResultEmpty(state:) passes
newState but the function ignores this parameter and uses currentState instead,
causing stale data usage. Fix this by modifying makeSearchResultEmpty(state:) to
use the passed-in state parameter rather than currentState, ensuring it
evaluates the correct, updated state. Optionally, remove the state parameter
from all call sites if you prefer to always use the currentState inside the
function.

Copy link
Member

@dongglehada dongglehada left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공유해주신 빌더 패턴을 적용하여 layout을 생성하는 protocol을 설계해주신것 같아서 좋습니다! 앞으로 계속 이 설계를 기반으로 발전시켜보면 좋을 것 같아요 :> 수고 많으셨습니다 !!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공유주신 코드리뷰의 역할을 하는 기능인가 보네요!!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안전한 서브스크립트 접근..!

self.section = existingSection
}

@discardableResult
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴파일러에서 경고 메시지를 이렇게 제한을 할 수도 있군요..!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protocol 을 분리하여 header를 정의하는 부분도 따로 채택할 수 있는 구조군요..!

@zzangzzangguy zzangzzangguy merged commit 543ccea into develop May 26, 2025
3 checks passed
@0Hooni 0Hooni deleted the refactor/#150-layout-abstract branch July 27, 2025 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🚀 feat 새로운 기능을 추가 🔄 refactor 프로덕션 코드 리팩토링, 파일 삭제, 네이밍 수정 및 폴더링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

반복되는 CompositionalLayout을 추상화합니다.

4 participants