Skip to content

[FEAT] 메인 TabView 및 탭별 NavigationStack 연결 - #36

Merged
devBambu merged 23 commits into
developfrom
feature#2
Aug 20, 2026
Merged

devBambu merged 23 commits into
developfrom
feature#2

Conversation

@devBambu

@devBambu devBambu commented Aug 20, 2026 •

Copy link
Copy Markdown
Owner

🎫 관련 이슈 (Linked Issues)

Closes #2

🛠 작업 내용 (What I did)

  • MohaView에 Home, Report, Settings 탭을 제공하는 기본 TabView 컨테이너를 구현했습니다.
  • 각 탭에 독립적인 NavigationStack과 Router를 연결했습니다.
  • 공통 내비게이션 동작을 NavigationRouter 프로토콜로 정의하고 탭별 Route 타입을 구성했습니다.
  • MainRouter에서 선택된 탭과 탭별 Router를 관리하고 Environment로 주입했습니다.

💻 구현 상세 (Implementation Details)

  • TabView(selection:)과 각 탭의 tag를 연결하여 선택 상태를 명시적으로 관리합니다.
  • Home, Report, Settings가 각각 독립적인 NavigationPath를 사용하도록 구성해 탭 전환 시 탐색 상태가 섞이지 않도록 했습니다.
  • push, pop, reset, popTo, replace를 공통 Router 동작으로 제공했습니다.
  • 탭별 navigationDestination에서 각 Route에 대응하는 화면을 생성하도록 분리했습니다.

📸 스크린샷 (Screenshots)

Home Report Settings
image image image

✅ 검증 (Verification)

  • iOS Simulator 대상 Debug 빌드 성공

🧐 리뷰 포인트 (Review Points)

  • MainRouter가 탭 선택 상태와 탭별 Router를 소유하는 구조가 적절한지 확인 부탁드립니다.
  • 탭별 NavigationPath 분리와 Environment 주입 방식이 이후 화면 확장에 적합한지 봐주세요.
  • NavigationRouter의 공통 탐색 동작과 Route 타입 구성이 명확한지 확인 부탁드립니다.

@devBambu devBambu self-assigned this Aug 20, 2026
@devBambu

Copy link
Copy Markdown
Owner Author

@sourcery-ai review

@sourcery-ai

sourcery-ai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

독립적인 내비게이션 스택에 연결된 탭 기반 컨테이너를 사용하는 새로운 메인 MohaView를 구현하고, 공통 라우팅 추상화를 도입하며, 이를 앱의 진입점으로 설정합니다.

File-Level Changes

Change Details Files
MohaView를 메인 TabView 컨테이너로 도입하고, MainRouter가 관리하는 탭 선택 및 라우터에 연결합니다.
  • 열거형 기반 TabItem을 사용하는 TabView를 갖춘 MohaView를 생성하고, 선택 바인딩을 MainRouter.selectedTab에 연결합니다.
  • HomeNavigationStack, ReportNavigationStack, SettingsNavigationStack을 탭 콘텐츠로 추가하고, 각 탭에 라벨과 시스템 이미지를 설정합니다.
  • 하위 뷰들이 라우팅 상태에 접근할 수 있도록 MainRouter를 환경에 주입합니다.
  • 앱의 진입점을 이전 ContentView 대신 MohaView를 표시하도록 업데이트합니다.
Moha/Moha/Sources/Features/MohaView.swift
Moha/Moha/Sources/MohaApp.swift
Home, Report, Settings 탭 각각에 대해 라우터 및 라우트 타입에 바인딩된 전용 NavigationStack 뷰를 추가합니다.
  • @bindable 라우터 프로퍼티를 가진 HomeNavigationStack, ReportNavigationStack, SettingsNavigationStack 뷰를 생성합니다.
  • 각 NavigationStack.path를 해당 라우터의 path에 바인딩합니다.
  • 각 탭에 대해 고유한 Route 타입을 사용하여 navigationDestination(for:)를 정의합니다.
  • 각 라우트에 대해 destinationView(for:)를 스텁으로 구현하며, 현재는 단일 .createTodo 케이스를 placeholder Text 뷰로 매핑합니다.
Moha/Moha/Sources/Navigation/NavigationStack/HomeNavigationStack.swift
Moha/Moha/Sources/Navigation/NavigationStack/ReportNavigationStack.swift
Moha/Moha/Sources/Navigation/NavigationStack/SettingsNavigationStack.swift
내비게이션 식별자를 표준화하기 위해 공통 Route 프로토콜과 각 탭에 대한 구체적인 라우트 enum을 정의합니다.
  • id와 Hashable 준수를 가지는 Route 프로토콜을 도입하고, 기본 익스텐션을 통해 이를 구현합니다.
  • 현재는 각각 단일 .createTodo 케이스와 매칭되는 id 문자열을 갖는 HomeRoute, ReportRoute, SettingsRoute enum을 추가합니다.
  • 탭별 스택에서 타입 안전한 내비게이션 destination을 위해 이러한 라우트 타입을 사용합니다.
Moha/Moha/Sources/Navigation/Route/Route.swift
Moha/Moha/Sources/Navigation/Route/HomeRoute.swift
Moha/Moha/Sources/Navigation/Route/ReportRoute.swift
Moha/Moha/Sources/Navigation/Route/SettingsRoute.swift
탭 선택과 탭별 라우터를 중앙에서 관리하는 MainRouter를 생성하고, 공통 내비게이션 동작을 제공하는 재사용 가능한 NavigationRouter 프로토콜을 정의합니다.
  • selectedTab 상태를 저장하고 HomeRouter, ReportRouter, SettingsRouter 인스턴스를 소유하는 @observable 클래스 MainRouter를 추가합니다.
  • 연관 타입 Destination과 NavigationPath 프로퍼티를 가지는 NavigationRouter 프로토콜을 도입합니다.
  • NavigationPath를 조작하는 push, pop, reset, popTo, replace 내비게이션 동작에 대한 기본 구현을 제공합니다.
  • 각각 Destination 라우트 타입과 NavigationPath 저장소를 가지며 NavigationRouter를 준수하는 HomeRouter, ReportRouter, SettingsRouter 클래스를 구현합니다.
Moha/Moha/Sources/Navigation/Router/MainRouter.swift
Moha/Moha/Sources/Navigation/Router/NavigationRouter.swift

Tips and commands

Sourcery와 상호작용하기

  • 새 리뷰 트리거: 풀 리퀘스트에 @sourcery-ai review 댓글을 남깁니다.
  • 논의 계속하기: Sourcery의 리뷰 댓글에 직접 답글을 남겨 논의를 이어갈 수 있습니다.
  • 리뷰 댓글로부터 GitHub 이슈 생성: 리뷰 댓글에 답글을 남겨 해당 댓글로부터 이슈 생성을 요청할 수 있습니다. 또는 리뷰 댓글에 @sourcery-ai issue로 답글을 남겨 그 댓글로부터 이슈를 생성할 수도 있습니다.
  • 풀 리퀘스트 제목 생성: 풀 리퀘스트 제목 어디든 @sourcery-ai를 작성해 언제든 제목을 생성할 수 있습니다. 또한 풀 리퀘스트에 @sourcery-ai title 댓글을 남겨 제목을 (재)생성할 수 있습니다.
  • 풀 리퀘스트 요약 생성: 풀 리퀘스트 본문 어디든 @sourcery-ai summary를 작성해 원하는 위치에 PR 요약을 언제든 생성할 수 있습니다. 또한 풀 리퀘스트에 @sourcery-ai summary 댓글을 남겨 요약을 (재)생성할 수 있습니다.
  • 리뷰어 가이드 생성: 풀 리퀘스트에 @sourcery-ai guide 댓글을 남겨 리뷰어 가이드를 언제든 (재)생성할 수 있습니다.
  • 모든 Sourcery 댓글 해결 처리: 풀 리퀘스트에 @sourcery-ai resolve 댓글을 남겨 모든 Sourcery 댓글을 해결된 것으로 표시합니다. 이미 모든 댓글을 반영했고 더 이상 보고 싶지 않을 때 유용합니다.
  • 모든 Sourcery 리뷰 해제: 풀 리퀘스트에 @sourcery-ai dismiss 댓글을 남겨 기존의 모든 Sourcery 리뷰를 해제합니다. 새 리뷰로 깔끔하게 다시 시작하고 싶을 때 특히 유용합니다. 새 리뷰를 트리거하려면 @sourcery-ai review 댓글을 다시 남기는 것을 잊지 마세요!

환경 맞춤 설정

대시보드에 접속하여:

  • Sourcery가 생성하는 풀 리퀘스트 요약, 리뷰어 가이드 등 다양한 리뷰 기능을 활성화 또는 비활성화합니다.
  • 리뷰 언어를 변경합니다.
  • 사용자 정의 리뷰 지침을 추가, 삭제 또는 편집합니다.
  • 기타 리뷰 설정을 조정합니다.

도움 받기

Original review guide in English

Reviewer's Guide

Implements a new main MohaView with a tab-based container wired to independent navigation stacks per tab, introduces a shared routing abstraction, and makes it the app’s entry point.

File-Level Changes

Change Details Files
Introduce MohaView as the main TabView container and wire it to a MainRouter-managed tab selection and routers.
  • Create MohaView with a TabView using an enum-based TabItem and selection binding to MainRouter.selectedTab.
  • Attach HomeNavigationStack, ReportNavigationStack, and SettingsNavigationStack as tab contents with labels and system images.
  • Inject MainRouter into the environment so child views can access routing state.
  • Update the app entry point to present MohaView instead of the previous ContentView.
Moha/Moha/Sources/Features/MohaView.swift
Moha/Moha/Sources/MohaApp.swift
Add dedicated NavigationStack views for Home, Report, and Settings tabs, each bound to its own router and route type.
  • Create HomeNavigationStack, ReportNavigationStack, and SettingsNavigationStack views with @bindable router properties.
  • Bind each NavigationStack.path to the corresponding router.path.
  • Define navigationDestination(for:) for each tab using its specific Route type.
  • Stub out destinationView(for:) per route, currently mapping a single .createTodo case to placeholder Text views.
Moha/Moha/Sources/Navigation/NavigationStack/HomeNavigationStack.swift
Moha/Moha/Sources/Navigation/NavigationStack/ReportNavigationStack.swift
Moha/Moha/Sources/Navigation/NavigationStack/SettingsNavigationStack.swift
Define a shared Route protocol and concrete route enums for each tab to standardize navigation identifiers.
  • Introduce a Route protocol with id and Hashable conformance implemented via a default extension.
  • Add HomeRoute, ReportRoute, and SettingsRoute enums, currently each with a single .createTodo case and matching id string.
  • Use these route types for type-safe navigation destinations in the tab-specific stacks.
Moha/Moha/Sources/Navigation/Route/Route.swift
Moha/Moha/Sources/Navigation/Route/HomeRoute.swift
Moha/Moha/Sources/Navigation/Route/ReportRoute.swift
Moha/Moha/Sources/Navigation/Route/SettingsRoute.swift
Create MainRouter as the central owner of tab selection and per-tab routers, and define a reusable NavigationRouter protocol with common navigation operations.
  • Add MainRouter @observable class that stores the selectedTab state and owns HomeRouter, ReportRouter, and SettingsRouter instances.
  • Introduce NavigationRouter protocol with an associated Destination type and a NavigationPath property.
  • Provide default implementations for push, pop, reset, popTo, and replace navigation operations manipulating the NavigationPath.
  • Implement HomeRouter, ReportRouter, and SettingsRouter classes conforming to NavigationRouter with their respective Destination route types and NavigationPath storage.
Moha/Moha/Sources/Navigation/Router/MainRouter.swift
Moha/Moha/Sources/Navigation/Router/NavigationRouter.swift

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 이 PR에서 이슈 한 가지를 발견했고, 전반적인 피드백을 몇 가지 남깁니다:

  • NavigationRouter의 API가 연관 타입 Destination 대신 any Route를 받도록 되어 있어서, 다른 탭의 라우트를 실수로 푸시하는 문제가 생길 수 있습니다. 각 라우터가 타입 안전성을 유지하고 서로 분리되도록, 이 메서드들을 Destination으로 제한하는 것을 고려해 주세요.
  • Route 프로토콜이 문자열 id와 커스텀 Hashable/Equatable 구현을 강제하고 있는데, 실제로는 enum이 합성된(con synthesized) 적합성으로 충분히 처리할 수 있습니다. 타입을 넘나드는 string 키가 꼭 필요하지 않다면, enum의 기본 Hashable을 사용하도록 단순화하거나 id를 더 강하게 타입 지정하면 보일러플레이트를 줄이고 잠재적인 ID 충돌도 줄일 수 있습니다.
  • MainRouter가 현재 MohaView.TabItem에 의존하고 있어, 핵심 라우팅 객체가 특정 뷰에 묶여 있습니다. TabItem을 별도의 타입(또는 모듈)로 분리하면 내비게이션 상태를 UI 레이어에서 분리할 수 있고, 라우터를 더 쉽게 재사용할 수 있습니다.
AI 에이전트를 위한 프롬프트
Please address the comments from this code review:

## Overall Comments
- `NavigationRouter`’s APIs accept `any Route` instead of the `Destination` associated type, which allows accidentally pushing routes from other tabs; consider constraining these methods to `Destination` to keep each router type-safe and isolated.
- The `Route` protocol forces a string `id` and custom `Hashable`/`Equatable` while the enums could use synthesized conformance; if you don’t need a cross-type string key, simplifying to the enum’s default `Hashable` or making `id` more strongly typed would reduce boilerplate and potential ID collisions.
- `MainRouter` currently depends on `MohaView.TabItem`, tying a core routing object to a specific view; extracting `TabItem` to a standalone type (or module) would decouple navigation state from the UI layer and make the router easier to reuse.

## Individual Comments

### Comment 1
<location path="Moha/Moha/Sources/Navigation/Router/NavigationRouter.swift" line_range="10-13" />
<code_context>
+
+import SwiftUI
+
+protocol NavigationRouter: AnyObject {
+    associatedtype Destination: Route
+    
+    var path: NavigationPath { get set }
+}
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** `Destination` 연관 타입이 정의되어 있지만 라우터 API에서 강제되지 않아, 라우터 간 라우트가 잘못 사용될 수 있습니다.

`NavigationRouter`는 `associatedtype Destination: Route`를 정의하고 있지만, `push(to:)`와 `replace(with:)` 같은 라우터 API는 여전히 `any Route`를 받습니다. 이 때문에 다른 라우터의 라우트를 전달할 수 있고(예: `ReportRoute`를 `HomeRouter`에 전달) 런타임에서야 실패하게 됩니다. 이런 API를 `Destination`을 사용하도록 변경하면(예: `func push(to route: Destination)`) 컴파일 타임에 타입 안전성을 보장하고, 연관 타입 설계와도 더 잘 맞게 됩니다.

Suggested implementation:

```
extension NavigationRouter {
    func push(to route: Destination) {

```

라우터 전체 API에 이 제안을 완전히 적용하려면 다음을 수행해야 합니다:
1. 현재 `any Route`를 받는 다른 모든 라우터 메서드들(예: `replace(with:)`, `insert(_:)` 등)을 `Destination`을 받도록 업데이트합니다.
2. 라우트를 동적으로 생성하는 오버로드나 헬퍼가 있다면, 다음 중 하나를 만족하도록 합니다:
   - 필요한 곳에서는 `Destination`을 제네릭으로 사용하거나,
   - `any Route` 대신 라우터의 `Destination` 타입으로 제한합니다.
3. 여전히 `any Route`를 전달하는 호출부가 있다면, 컴파일러가 올바른 라우트 사용을 강제할 수 있도록 각 라우터의 `Destination` 타입을 사용하도록 업데이트합니다.
</issue_to_address>

Sourcery는 오픈 소스에 무료로 제공됩니다 - 리뷰가 마음에 드셨다면 공유를 고려해 주세요 ✨
제가 더 유용해질 수 있도록 도와주세요! 각 댓글에 👍 또는 👎를 눌러 주시면, 그 피드백을 바탕으로 리뷰 품질을 개선하겠습니다.
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • NavigationRouter’s APIs accept any Route instead of the Destination associated type, which allows accidentally pushing routes from other tabs; consider constraining these methods to Destination to keep each router type-safe and isolated.
  • The Route protocol forces a string id and custom Hashable/Equatable while the enums could use synthesized conformance; if you don’t need a cross-type string key, simplifying to the enum’s default Hashable or making id more strongly typed would reduce boilerplate and potential ID collisions.
  • MainRouter currently depends on MohaView.TabItem, tying a core routing object to a specific view; extracting TabItem to a standalone type (or module) would decouple navigation state from the UI layer and make the router easier to reuse.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- `NavigationRouter`’s APIs accept `any Route` instead of the `Destination` associated type, which allows accidentally pushing routes from other tabs; consider constraining these methods to `Destination` to keep each router type-safe and isolated.
- The `Route` protocol forces a string `id` and custom `Hashable`/`Equatable` while the enums could use synthesized conformance; if you don’t need a cross-type string key, simplifying to the enum’s default `Hashable` or making `id` more strongly typed would reduce boilerplate and potential ID collisions.
- `MainRouter` currently depends on `MohaView.TabItem`, tying a core routing object to a specific view; extracting `TabItem` to a standalone type (or module) would decouple navigation state from the UI layer and make the router easier to reuse.

## Individual Comments

### Comment 1
<location path="Moha/Moha/Sources/Navigation/Router/NavigationRouter.swift" line_range="10-13" />
<code_context>
+
+import SwiftUI
+
+protocol NavigationRouter: AnyObject {
+    associatedtype Destination: Route
+    
+    var path: NavigationPath { get set }
+}
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The `Destination` associated type is defined but not enforced in the router API, which can lead to cross-router route misuse.

`NavigationRouter` defines `associatedtype Destination: Route`, but router APIs like `push(to:)` and `replace(with:)` still take `any Route`. This allows passing routes from other routers (e.g. `ReportRoute` into `HomeRouter`) and only failing at runtime. Updating these APIs to use `Destination` (e.g. `func push(to route: Destination)`) would enforce compile-time type safety and align with the associated type design.

Suggested implementation:

```
extension NavigationRouter {
    func push(to route: Destination) {

```

To fully implement the suggestion across the router API, you should:
1. Update all other router methods that currently accept `any Route` (e.g. `replace(with:)`, `insert(_:)`, etc.) to instead accept `Destination`.
2. If there are overloads or helpers that construct routes dynamically, ensure they either:
   - Are generic over `Destination` where appropriate, or
   - Are constrained to the router’s `Destination` type instead of `any Route`.
3. If any call sites still pass `any Route`, update them to use the specific router’s `Destination` type so the compiler can enforce the correct route usage.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread Moha/Moha/Sources/Navigation/Router/NavigationRouter.swift
- 프로젝트 경로 수정
- 시뮬레이터 수정
- 누락 기호 추가 ( ' \. ')
- any Route -> Destination 수정으로 인해 함수 동작 오류 방지
- 해당값이 없어도 enum은 Hashable 채택 가능
- Router에서의 associatedType을 제한하기 위해 Route 프로토콜은 삭제하지 않음
@devBambu

Copy link
Copy Markdown
Owner Author

NavigationRouter의 API가 연관 타입 Destination 대신 any Route를 받도록 되어 있어서, 다른 탭의 라우트를 실수로 푸시하는 문제가 생길 수 있습니다. 각 라우터가 타입 안전성을 유지하고 서로 분리되도록, 이 메서드들을 Destination으로 제한하는 것을 고려해 주세요.

-> associatedType 수정 완료

Route 프로토콜이 문자열 id와 커스텀 Hashable/Equatable 구현을 강제하고 있는데, 실제로는 enum이 합성된(con synthesized) 적합성으로 충분히 처리할 수 있습니다. 타입을 넘나드는 string 키가 꼭 필요하지 않다면, enum의 기본 Hashable을 사용하도록 단순화하거나 id를 더 강하게 타입 지정하면 보일러플레이트를 줄이고 잠재적인 ID 충돌도 줄일 수 있습니다.

-> Route 프로토콜의 id 속성 및 hash 함수 삭제 완료. 단, Router 프로토콜의 associatedType Destination을 Route로만 제한하기 위해 프로토콜 자체를 삭제하지는 않았음.

MainRouter가 현재 MohaView.TabItem에 의존하고 있어, 핵심 라우팅 객체가 특정 뷰에 묶여 있습니다. TabItem을 별도의 타입(또는 모듈)로 분리하면 내비게이션 상태를 UI 레이어에서 분리할 수 있고, 라우터를 더 쉽게 재사용할 수 있습니다.

-> TabItem 의존성 분리 완료

@devBambu
devBambu merged commit e480c50 into develop Aug 20, 2026
2 checks passed
@devBambu
devBambu deleted the feature#2 branch August 20, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant