Conversation
📝 WalkthroughWalkthrough챌린지 기능의 전체 흐름을 구현하기 위해 여러 뷰와 에셋을 추가합니다. 루틴 선택, 미션 선택, 진행 상황 추적을 포함한 챌린지 진행 화면을 구성하고, 네비게이션 및 탭 바 상태를 관리하는 코디네이터를 통합합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant StartChallengeView as StartChallenge<br/>View
participant SelectRoutineView as SelectRoutine<br/>View
participant SelectMissionView as SelectMission<br/>View
participant ChallengeProgressView as ChallengeProgress<br/>View
participant LoadingView
participant ChallengeCoordinator
participant TabBarCoordinator
User->>StartChallengeView: 챌린지 시작 버튼 클릭
StartChallengeView->>TabBarCoordinator: 탭 바 숨김 설정
StartChallengeView->>ChallengeCoordinator: selectRoutine 네비게이션
ChallengeCoordinator->>SelectRoutineView: 루틴 선택 화면 표시
User->>SelectRoutineView: 루틴 선택 (ViewModel)
SelectRoutineView->>SelectRoutineView: nextButtonState 활성화
User->>SelectRoutineView: 다음 버튼
SelectRoutineView->>ChallengeCoordinator: selectMission 네비게이션
ChallengeCoordinator->>SelectMissionView: 미션 선택 화면 표시
User->>SelectMissionView: 미션 선택 (다중 선택)
SelectMissionView->>SelectMissionView: selectedStates 업데이트
User->>SelectMissionView: 다음 버튼
SelectMissionView->>ChallengeCoordinator: challengeProgress 네비게이션
ChallengeCoordinator->>LoadingView: 로딩 화면 표시
LoadingView->>LoadingView: 3초 대기 (moveNextAfterDelay)
LoadingView->>ChallengeCoordinator: selectMission 네비게이션
ChallengeCoordinator->>ChallengeProgressView: 진행 상황 추적 화면 표시
ChallengeProgressView->>ChallengeProgressView: CherryLevel 계산 및 UI 렌더링
User->>ChallengeProgressView: TO-DO 항목 체크 및 완료
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swift (1)
24-28: 배경이 둥근 모서리에 클리핑되지 않을 수 있습니다.
.background수정자가 직접 적용되어 있지만, overlay의RoundedRectangle(cornerRadius: 10)과 달리 배경색이 모서리에서 잘리지 않습니다. 이로 인해 배경색이 둥근 테두리 바깥으로 보일 수 있습니다.🔧 배경에 cornerRadius 적용 제안
- .background(isSelected ? .red100 : .gray0) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(isSelected ? .red100 : .gray0) + )또는
clipShape를 추가하는 방법도 있습니다:.background(isSelected ? .red100 : .gray0) + .clipShape(RoundedRectangle(cornerRadius: 10))
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`:
- Around line 144-146: The CherrishButton instance with title "오늘 미션 종료하기" has
an empty action closure; either implement the intended behavior inside that
closure (e.g., call the view model method like viewModel.completeTodayMission(),
dispatch navigation or state updates, and update buttonState) or, if it’s
intentionally left for later, add a clear TODO comment inside the closure (e.g.,
// TODO: implement finish-today-mission action — track with issue #<id>) so
future reviewers know it’s pending; locate the empty closure on the
CherrishButton usage and replace it with the action implementation or the TODO
comment as appropriate.
- Around line 63-72: selectedStates is hardcoded to a fixed length and can go
out of sync with missions; replace the separate arrays by integrating selection
into a single model (e.g., a Mission struct with id/title/isSelected) or
initialize selectedStates from missions.count and update it whenever missions
changes; update ChallengeProgressView (and SelectMissionView analogously) to use
the new model/mission.isSelected property (or the dynamic selectedStates) so
indices remain consistent and avoid range errors.
- Line 108: The displayed progressRate is a Double and may show decimal
fractions; update the string interpolation in the TypographyText call that
currently uses "챌린지 달성률 \(progressRate)%" to format it as an integer (e.g., use
Int(progressRate) or String(format: "%.0f", progressRate) or
Int(progressRate.rounded()) ) so the view shows a whole percent value like "챌린지
달성률 76%".
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift`:
- Around line 33-45: The background Task started in moveNextAfterDelay() can
survive when the view is dismissed, causing unwanted navigation; store the Task
in a cancellable reference (e.g., an `@State` or `@StateObject` property like
navigationTask: Task<Void, Never>?), assign the Task when creating it in
moveNextAfterDelay(), and cancel it in onDisappear (navigationTask?.cancel());
also handle cancellation inside the Task (await Task.sleep can throw
CancellationError) and only call challengeCoordinator.push(.selectMission) when
the Task has not been cancelled.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift`:
- Around line 39-41: Remove the useless .padding(.horizontal, 33.adjustedW)
applied to the Spacer() instances in SelectMissionView (the Spacer() calls
around the mission content); Spacer only expands and ignores padding, so delete
the .padding on those Spacer()s and, if horizontal inset is intended, move the
.padding(.horizontal, 33.adjustedW) to the containing view (e.g., the
HStack/VStack or the mission card view) so the layout reflects the intended
horizontal spacing.
- Around line 14-23: selectedStates is initialized with a hardcoded count (6)
which can go out of sync with missions; change the initialization to derive its
size from missions (e.g., Array(repeating: false, count: missions.count) or
initialize selectedStates lazily from missions) or refactor by introducing a
Mission struct (struct Mission with id, text, isSelected) and replace the
separate missions and selectedStates with a single `@State` array of Mission so
selection state travels with each mission (update usages of missions and
selectedStates accordingly, e.g., map or binding to mission.isSelected).
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/CherrishNavigationBar.swift`:
- Line 60: Replace the vertical-based scaler with the width-based one for
horizontal padding: in CherrishNavigationBar locate the .padding(.horizontal,
8.adjustedH) expression and change adjustedH to adjustedW so horizontal padding
uses the width-adjusted value (i.e., .padding(.horizontal, 8.adjustedW)).
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swift`:
- Around line 15-23: The outer VStack(alignment: .leading) in MissionCard.swift
is redundant because it only wraps a single HStack; remove the VStack wrapper
and its corresponding closing brace, leaving the HStack as the top-level view
(or apply alignment/padding directly on the HStack if needed) so Image,
TypographyText and Spacer remain unchanged; if the VStack was intentionally kept
for future expansion, add a brief comment above it indicating that intent
instead of leaving an unused wrapper.
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View`+.swift:
- Around line 10-21: The extension method
highlight(_:highlightColor:normalText:) on View ignores self and returns a brand
new HStack; replace it with a standalone SwiftUI View struct (e.g., struct
HighlightText: View) that has stored properties highlightText: String,
normalText: String, highlightColor: Color = .red700 and implements var body ->
some View returning the HStack with TypographyText(...) so callers use
HighlightText(highlightText:..., normalText:...) instead of
someView.highlight(...); remove or deprecate the View extension to avoid
misleading semantics and update call sites accordingly.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (11)
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challengeImage.imageset/challengeImage.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_0.imageset/challenge_gaugebar_0.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_1.imageset/challenge_gaugebar_1.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_2.imageset/challenge_gaugebar_2.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_3.imageset/challenge_gaugebar_3.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_4.imageset/challenge_gaugebar_4.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry0.imageset/cherry0.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry1.imageset/cherry1.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry2.imageset/cherry2.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry3.imageset/cherry3.svgis excluded by!**/*.svgCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry4.imageset/cherry4.svgis excluded by!**/*.svg
📒 Files selected for processing (20)
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challengeImage.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_0.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_1.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_2.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_3.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_4.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry0.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry1.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry2.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry3.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry4.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/StartChallengeView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Global/CherrishNavigationBar.swiftCherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swiftCherrish-iOS/Cherrish-iOS/Presentation/Global/Components/SelectionChip.swiftCherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+.swift
🧰 Additional context used
🧬 Code graph analysis (6)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/SelectionChip.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (2)
red200(69-71)gray0(9-11)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (2)
red700(89-91)gray800(41-43)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Coordinator/AppCoordinatorProtocol.swift (2)
pop(22-24)popToRoot(26-28)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift (2)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (2)
body(9-17)cherrishShadow(21-23)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (7)
gray1000(49-51)gray700(37-39)gray900(45-47)gray800(41-43)gray300(21-23)red200(69-71)gray0(9-11)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift (4)
Cherrish-iOS/Cherrish-iOS/Presentation/Coordinator/AppCoordinatorProtocol.swift (1)
pop(22-24)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+.swift (1)
highlight(11-20)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (2)
gray800(41-43)gray600(33-35)Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinator.swift (1)
push(24-26)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (4)
gray800(41-43)gray700(37-39)red100(65-67)gray0(9-11)
🔇 Additional comments (15)
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry1.imageset/Contents.json (1)
1-12: LGTM!Xcode 에셋 카탈로그 표준 형식을 따르는 올바른 Contents.json 파일입니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_1.imageset/Contents.json (1)
1-12: LGTM!게이지바 에셋 시리즈의 일부로, 표준 Xcode 에셋 카탈로그 형식을 따릅니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_0.imageset/Contents.json (1)
1-12: LGTM!게이지바 에셋 시리즈의 첫 번째 항목으로, 표준 형식을 따릅니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_4.imageset/Contents.json (1)
1-12: LGTM!게이지바 에셋 시리즈(0-4)의 마지막 항목으로, 100% 진행률에 해당하는 에셋입니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry3.imageset/Contents.json (1)
1-12: LGTM!체리 캐릭터 이미지 시리즈의 일부로, CherryLevel enum의 "pang" 레벨(75-100% 진행률)에 해당하는 에셋입니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challengeImage.imageset/Contents.json (1)
1-12: LGTM!표준 Xcode 에셋 카탈로그 JSON 형식입니다. challengeImage.svg를 universal idiom으로 올바르게 선언하고 있습니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry4.imageset/Contents.json (1)
1-12: LGTM!cherry4.svg 에셋이 올바른 형식으로 선언되었습니다. PR 목표에 명시된 CherryLevel enum의 5단계(mong, bbo, chok, pang, ggu) 중 최고 레벨인 'ggu'에 해당하는 이미지로 보입니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry2.imageset/Contents.json (1)
1-12: LGTM!cherry2.svg 에셋이 올바른 형식으로 선언되었습니다. CherryLevel의 'chok' 단계(50.0..<75.0)에 해당하는 이미지입니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_3.imageset/Contents.json (1)
1-12: LGTM!challenge_gaugebar_3.svg 에셋이 올바른 형식으로 선언되었습니다. ChallengeProgressView의 진행률 표시에 사용되는 게이지바 이미지 시리즈의 일부입니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry0.imageset/Contents.json (1)
1-12: LGTM!표준 Xcode 에셋 카탈로그 구조입니다. cherry0.svg를 universal idiom으로 올바르게 참조하고 있습니다.
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/challenge_gaugebar_2.imageset/Contents.json (1)
1-12: LGTM!게이지바 에셋이 올바른 형식으로 추가되었습니다.
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/SelectionChip.swift (1)
21-24: LGTM!RoundedRectangle 배경으로 변경하여 overlay의 stroke와 일관된 cornerRadius(10)를 사용하고 있습니다. 스타일링이 적절합니다.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/StartChallengeView.swift (1)
12-12: LGTM!탭바 숨김 처리가 적절하게 구현되었습니다. 챌린지 시작 시 탭바를 숨기고 SelectRoutineView에서 뒤로 가거나 닫을 때 다시 보여주는 일관된 패턴이 잘 적용되었습니다.
Also applies to: 40-43
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift (1)
33-33: LGTM!탭바 표시 상태 관리가 적절하게 구현되었습니다. 뒤로가기와 닫기 동작 시 탭바를 다시 표시하는 로직이 일관성 있게 적용되었습니다.
Also applies to: 45-52
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift (1)
10-52: CherryLevel 열거형이 잘 구현되었습니다.PR 설명에 명시된 대로 진행률을 5단계 레벨로 매핑하는 로직이 명확하고 확장 가능하게 구현되었습니다.
from(progressRate:)정적 메서드를 통한 팩토리 패턴 사용도 적절합니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| @State private var missions: [String] = [ | ||
| "진정 토너 + 세럼", | ||
| "진정 토너 + 세럼", | ||
| "선크림 3번 바르기", | ||
| "선크림 3번 바르기", | ||
| "선크림 3번 바르기", | ||
| "선크림 3번 바르기" | ||
| ] | ||
|
|
||
| @State private var selectedStates: [Bool] = Array(repeating: false, count: 6) |
There was a problem hiding this comment.
selectedStates 배열이 missions와 동기화되지 않았습니다.
SelectMissionView와 동일한 문제입니다. 배열 크기가 하드코딩되어 있어 missions 배열 변경 시 인덱스 범위 초과 오류가 발생할 수 있습니다. 미션과 선택 상태를 하나의 모델로 통합하는 것을 권장합니다.
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`
around lines 63 - 72, selectedStates is hardcoded to a fixed length and can go
out of sync with missions; replace the separate arrays by integrating selection
into a single model (e.g., a Mission struct with id/title/isSelected) or
initialize selectedStates from missions.count and update it whenever missions
changes; update ChallengeProgressView (and SelectMissionView analogously) to use
the new model/mission.isSelected property (or the dynamic selectedStates) so
indices remain consistent and avoid range errors.
| .padding(.vertical, 14.adjustedH) | ||
| VStack { | ||
| HStack { | ||
| TypographyText("챌린지 달성률 \(progressRate)%", style: .body1_m_14, color: .gray900) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Double 타입의 진행률이 소수점으로 표시될 수 있습니다.
progressRate가 Double 타입이므로 "챌린지 달성률 75.5%"처럼 소수점이 표시될 수 있습니다. 정수로 표시하려면 포맷팅이 필요합니다.
💡 정수 포맷팅 제안
-TypographyText("챌린지 달성률 \(progressRate)%", style: .body1_m_14, color: .gray900)
+TypographyText("챌린지 달성률 \(Int(progressRate))%", style: .body1_m_14, color: .gray900)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| TypographyText("챌린지 달성률 \(progressRate)%", style: .body1_m_14, color: .gray900) | |
| TypographyText("챌린지 달성률 \(Int(progressRate))%", style: .body1_m_14, color: .gray900) |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`
at line 108, The displayed progressRate is a Double and may show decimal
fractions; update the string interpolation in the TypographyText call that
currently uses "챌린지 달성률 \(progressRate)%" to format it as an integer (e.g., use
Int(progressRate) or String(format: "%.0f", progressRate) or
Int(progressRate.rounded()) ) so the view shows a whole percent value like "챌린지
달성률 76%".
| CherrishButton(title: "오늘 미션 종료하기", type: .next, state: .constant(buttonState)){ | ||
|
|
||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
버튼 액션이 비어있습니다.
"오늘 미션 종료하기" 버튼의 액션이 구현되지 않았습니다. 의도된 플레이스홀더라면 TODO 주석을 추가하여 추후 구현이 필요함을 명시해주세요.
향후 구현이 필요한 경우, 이 작업을 추적하기 위한 이슈를 생성해 드릴까요?
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`
around lines 144 - 146, The CherrishButton instance with title "오늘 미션 종료하기" has
an empty action closure; either implement the intended behavior inside that
closure (e.g., call the view model method like viewModel.completeTodayMission(),
dispatch navigation or state updates, and update buttonState) or, if it’s
intentionally left for later, add a clear TODO comment inside the closure (e.g.,
// TODO: implement finish-today-mission action — track with issue #<id>) so
future reviewers know it’s pending; locate the empty closure on the
CherrishButton usage and replace it with the action implementation or the TODO
comment as appropriate.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift
Show resolved
Hide resolved
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift
Show resolved
Hide resolved
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift
Outdated
Show resolved
Hide resolved
Cherrish-iOS/Cherrish-iOS/Presentation/Global/CherrishNavigationBar.swift
Outdated
Show resolved
Hide resolved
| VStack(alignment: .leading) { | ||
|
|
||
| HStack { | ||
| Image(isSelected ? .radiobtnSelected : .radiobtnDefault) | ||
| .padding(.leading, 14.adjustedW) | ||
| .padding(.vertical, 14.adjustedH) | ||
| TypographyText(missionText, style: .body1_r_14, color: isSelected ? .gray800 : .gray700) | ||
| Spacer() | ||
| Image(isSelected ? "radiobtn_selected" : "radiobtn_default") | ||
| } | ||
|
|
||
| Spacer() | ||
|
|
||
| Text(missionText) | ||
| .typography(.body1_m_14) | ||
| .foregroundStyle(isSelected ? .gray800 : .gray700) | ||
| .padding(.leading, 8.adjustedW) | ||
| .padding(.bottom, 6.adjustedH) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
불필요한 VStack 래퍼 고려
VStack(alignment: .leading)이 단일 HStack만 감싸고 있습니다. 레이아웃 단순화를 위해 VStack 제거를 고려해볼 수 있습니다. 다만, 향후 확장성을 위해 의도적으로 유지한 것이라면 현재 상태도 괜찮습니다.
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swift`
around lines 15 - 23, The outer VStack(alignment: .leading) in MissionCard.swift
is redundant because it only wraps a single HStack; remove the VStack wrapper
and its corresponding closing brace, leaving the HStack as the top-level view
(or apply alignment/padding directly on the HStack if needed) so Image,
TypographyText and Spacer remain unchanged; if the VStack was intentionally kept
for future expansion, add a brief comment above it indicating that intent
instead of leaving an unused wrapper.
| extension View { | ||
| func highlight( | ||
| highlightText: String, | ||
| highlightColor: Color = .red700, | ||
| normalText: String | ||
| ) -> some View { | ||
| HStack { | ||
| TypographyText(highlightText, style: .title1_sb_18, color: highlightColor) | ||
| TypographyText(normalText, style: .title1_sb_18, color: .gray800) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
View extension이 self를 사용하지 않습니다.
이 함수는 View extension의 메서드로 정의되어 있지만, self를 전혀 사용하지 않고 완전히 새로운 HStack을 반환합니다. 이는 extension 메서드의 의미론적 목적에 맞지 않습니다.
someView.highlight(...)를 호출하면 someView는 무시되고 새로운 뷰가 반환됩니다.
♻️ 독립적인 View struct로 변경 제안
-extension View {
- func highlight(
- highlightText: String,
- highlightColor: Color = .red700,
- normalText: String
- ) -> some View {
- HStack {
- TypographyText(highlightText, style: .title1_sb_18, color: highlightColor)
- TypographyText(normalText, style: .title1_sb_18, color: .gray800)
- }
- }
-}
+struct HighlightText: View {
+ let highlightText: String
+ let highlightColor: Color
+ let normalText: String
+
+ init(
+ highlightText: String,
+ highlightColor: Color = .red700,
+ normalText: String
+ ) {
+ self.highlightText = highlightText
+ self.highlightColor = highlightColor
+ self.normalText = normalText
+ }
+
+ var body: some View {
+ HStack {
+ TypographyText(highlightText, style: .title1_sb_18, color: highlightColor)
+ TypographyText(normalText, style: .title1_sb_18, color: .gray800)
+ }
+ }
+}사용 예시:
// Before: Text("").highlight(highlightText: "강조", normalText: "일반")
// After: HighlightText(highlightText: "강조", normalText: "일반")🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View`+.swift around
lines 10 - 21, The extension method highlight(_:highlightColor:normalText:) on
View ignores self and returns a brand new HStack; replace it with a standalone
SwiftUI View struct (e.g., struct HighlightText: View) that has stored
properties highlightText: String, normalText: String, highlightColor: Color =
.red700 and implements var body -> some View returning the HStack with
TypographyText(...) so callers use HighlightText(highlightText:...,
normalText:...) instead of someView.highlight(...); remove or deprecate the View
extension to avoid misleading semantics and update call sites accordingly.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`:
- Around line 10-52: CherryLevel uses rawValue in cherryImage but levelNumber in
progressImage; make them consistent by using the same identifier across both
computed properties (either rawValue or levelNumber). Update either cherryImage
or progressImage to reference the other symbol (prefer using the explicit
levelNumber computed property for clarity) so both Image initializers use the
same value source; adjust Image("cherry\(...)") and
Image("challenge_gaugebar_\(...)") accordingly in the CherryLevel enum's
cherryImage and progressImage.
- Line 97: Replace the hardcoded image name construction with the enum's
existing computed property: where Image("cherry\\(cherryLevel.levelNumber)") is
used, use the CherryLevel.cherryImage property (i.e.,
Image(cherryLevel.cherryImage)) so the view relies on the CherryLevel enum's
cherryImage computed property for consistency and maintainability.
- Around line 56-61: The hardcoded view state (progressRate, remainMissions,
isChecked) must be moved out of the view and supplied by a ViewModel or
initializer: create a ChallengeProgressViewModel (ObservableObject) exposing
`@Published` properties progressRate: Double, remainMissions: Int, isChecked: Bool
(or expose Bindings if parent needs two‑way control), inject it into
ChallengeProgressView as `@ObservedObject` var viewModel (or add init parameters
for let progressRate: Double, let remainMissions: Int and Binding<Bool>
isChecked) and update the computed cherryLevel to call
CherryLevel.from(progressRate: viewModel.progressRate) (or from the init
property). Ensure all usages of progressRate, remainMissions, and isChecked
within ChallengeProgressView reference the viewModel properties or initializer
parameters so the UI reacts to external data changes.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/SelectRoutineViewModel.swift`:
- Line 39: Remove the redundant explicit nil initializer for the optional
published property: in SelectRoutineViewModel change the declaration of
selectedRoutine (type RoutineType?) to omit "= nil" so it reads just "@Published
var selectedRoutine: RoutineType?" to satisfy SwiftLint's
redundant_optional_initialization rule.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift`:
- Around line 42-52: The nested Task wrapper in moveNextAfterDelay prevents
navigationTask from being set immediately, breaking cancellation; remove the
outer Task and assign navigationTask directly (navigationTask = Task { ... }) so
cancellation from onDisappear (navigationTask?.cancel()) takes effect, keep the
sleep, guard !Task.isCancelled check, and await MainActor.run {
challengeCoordinator.push(.selectMission) } inside that single Task.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift`:
- Around line 82-84: Preview crashes because SelectRoutineView expects
EnvironmentObjects challengeCoordinator and tabBarCoordinator that aren’t
provided; update the Preview provider (the `#Preview` / Preview struct for
SelectRoutineView) to inject mock or default instances via
.environmentObject(...) for both challengeCoordinator and tabBarCoordinator so
the view can render in Xcode previews (reference SelectRoutineView,
challengeCoordinator, tabBarCoordinator, and the `#Preview` block).
♻️ Duplicate comments (5)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift (3)
63-72:selectedStates배열이missions와 동기화되지 않았습니다.이전 리뷰에서 지적된 것처럼,
selectedStates의 크기가 6으로 하드코딩되어 있어missions배열 변경 시 인덱스 범위 초과 오류가 발생할 수 있습니다.
109-109:Double타입의 진행률이 소수점으로 표시될 수 있습니다.이전 리뷰에서 지적된 것처럼,
progressRate가Double타입이므로 소수점이 표시될 수 있어 정수 포맷팅이 필요합니다.
145-147: 버튼 액션이 비어있습니다.이전 리뷰에서 지적된 것처럼, "오늘 미션 종료하기" 버튼의 액션이 구현되지 않았습니다. TODO 주석을 추가하거나 실제 동작을 구현해주세요.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift (2)
14-27:selectedStates크기를missions와 동기화하세요.
Line 14-27에서selectedStates가 6으로 하드코딩되어 있고, Line 62-67에서 인덱스 바인딩을 사용해 미션 수가 바뀌면 인덱스 범위 초과로 크래시가 날 수 있습니다.missions.count에 맞춰 동기화하세요.🔧 동기화 예시
-@State private var selectedStates: [Bool] = Array(repeating: false, count: 6) +@State private var selectedStates: [Bool] = [] ... - .padding(.horizontal, 34.adjustedW) + .padding(.horizontal, 34.adjustedW) + .onAppear { + if selectedStates.count != missions.count { + selectedStates = Array(repeating: false, count: missions.count) + } + }Also applies to: 62-67
71-73:Spacer()에 대한.padding(.horizontal)은 효과가 없습니다.
Line 71-73의 가로 패딩은 레이아웃에 영향을 주지 않으므로 제거하세요.♻️ 정리 제안
Spacer() .frame(height: 48.adjustedH) - .padding(.horizontal, 33.adjustedW)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (6)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/SelectRoutineViewModel.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Global/CherrishNavigationBar.swift
🧰 Additional context used
🧬 Code graph analysis (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (1)
body(9-17)Cherrish-iOS/Cherrish-iOS/Presentation/Coordinator/AppCoordinatorProtocol.swift (2)
pop(22-24)popToRoot(26-28)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (2)
gray1000(49-51)gray700(37-39)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+.swift (1)
highlight(11-20)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (2)
gray800(41-43)gray600(33-35)Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinator.swift (1)
push(24-26)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift (2)
Cherrish-iOS/Cherrish-iOS/Presentation/Coordinator/AppCoordinatorProtocol.swift (2)
pop(22-24)popToRoot(26-28)Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/SelectRoutineViewModel.swift (1)
select(45-47)
🪛 SwiftLint (0.57.0)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/SelectRoutineViewModel.swift
[Warning] 39-39: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🔇 Additional comments (8)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/CherrishNavigationBar.swift (1)
59-60: LGTM!네비게이션 바에 수평 패딩을 적용하고,
adjustedW(width 기반 조정값)를 사용한 것이 적절합니다. 코드베이스 전반의 패딩 컨벤션과 일치합니다.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift (1)
14-40: UI 레이아웃 및 라이프사이클 처리가 적절합니다.
highlight()View extension 활용,onAppear/onDisappear라이프사이클 핸들러 연결, 그리고 전체적인 레이아웃 구조가 잘 구성되어 있습니다.moveNextAfterDelay()함수의 중첩 Task 문제만 수정되면 의도한 대로 동작할 것입니다.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/SelectRoutineViewModel.swift (1)
11-31: RoutineType 정의 및 타이틀 매핑 좋습니다.케이스 구성과 타이틀 매핑이 UI 요구사항을 충족하며, 식별자도 명확합니다.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift (4)
12-20: 뷰모델 기반 상태 분리와 그리드 구성 깔끔합니다.상태 관리가 단순해지고 UI 바인딩이 명확해졌습니다.
27-34: 탭바 가시성 복구 처리 좋습니다.뒤로가기/루트 이동 시 탭바를 복구하는 흐름이 일관적입니다.
49-78: 루틴 그리드 및 선택 바인딩 구현 좋습니다.선택 상태가 뷰모델에 일관되게 연결되어 유지보수성이 높습니다.
60-64: 버튼 상태 바인딩 적절합니다.선택 유무에 따른 상태 반영이 명확합니다.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift (1)
75-77: 버튼 상태 계산과 네비게이션 흐름이 명확합니다.
Line 75-77의 버튼 액션과 상태 연결은 의도대로 잘 구성되어 있습니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| enum CherryLevel: Int { | ||
| case mong = 0 | ||
| case bbo | ||
| case chok | ||
| case pang | ||
| case ggu | ||
|
|
||
| var levelNumber: Int { rawValue } | ||
|
|
||
| static func from(progressRate: Double) -> CherryLevel { | ||
| switch progressRate { | ||
| case 0.0..<25.0: | ||
| return .mong | ||
| case 25.0..<50.0: | ||
| return .bbo | ||
| case 50.0..<75.0: | ||
| return .chok | ||
| case 75.0..<100.0: | ||
| return .pang | ||
| case 100.0...: | ||
| return .ggu | ||
| default: | ||
| return .mong | ||
| } | ||
| } | ||
|
|
||
| var name: String { | ||
| switch self { | ||
| case .mong: return "몽롱체리" | ||
| case .bbo: return "뽀득체리" | ||
| case .chok: return "촉촉체리" | ||
| case .pang: return "팡팡체리" | ||
| case .ggu: return "꾸꾸체리" | ||
| } | ||
| } | ||
|
|
||
| var cherryImage: Image { | ||
| Image("cherry\(rawValue)") | ||
| } | ||
| var progressImage: Image { | ||
| Image("challenge_gaugebar_\(levelNumber)") | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
CherryLevel enum 구현이 잘 되어 있습니다.
진행률 기반 레벨 매핑 로직이 명확하고, 연관된 이미지와 이름을 computed property로 제공하는 구조가 좋습니다.
다만 작은 일관성 개선 사항이 있습니다:
- Line 47:
cherryImage는rawValue를 사용 - Line 50:
progressImage는levelNumber를 사용
둘 다 동일한 값이지만, 일관성을 위해 통일하는 것이 좋습니다.
♻️ 일관성 개선 제안
var cherryImage: Image {
- Image("cherry\(rawValue)")
+ Image("cherry\(levelNumber)")
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| enum CherryLevel: Int { | |
| case mong = 0 | |
| case bbo | |
| case chok | |
| case pang | |
| case ggu | |
| var levelNumber: Int { rawValue } | |
| static func from(progressRate: Double) -> CherryLevel { | |
| switch progressRate { | |
| case 0.0..<25.0: | |
| return .mong | |
| case 25.0..<50.0: | |
| return .bbo | |
| case 50.0..<75.0: | |
| return .chok | |
| case 75.0..<100.0: | |
| return .pang | |
| case 100.0...: | |
| return .ggu | |
| default: | |
| return .mong | |
| } | |
| } | |
| var name: String { | |
| switch self { | |
| case .mong: return "몽롱체리" | |
| case .bbo: return "뽀득체리" | |
| case .chok: return "촉촉체리" | |
| case .pang: return "팡팡체리" | |
| case .ggu: return "꾸꾸체리" | |
| } | |
| } | |
| var cherryImage: Image { | |
| Image("cherry\(rawValue)") | |
| } | |
| var progressImage: Image { | |
| Image("challenge_gaugebar_\(levelNumber)") | |
| } | |
| } | |
| enum CherryLevel: Int { | |
| case mong = 0 | |
| case bbo | |
| case chok | |
| case pang | |
| case ggu | |
| var levelNumber: Int { rawValue } | |
| static func from(progressRate: Double) -> CherryLevel { | |
| switch progressRate { | |
| case 0.0..<25.0: | |
| return .mong | |
| case 25.0..<50.0: | |
| return .bbo | |
| case 50.0..<75.0: | |
| return .chok | |
| case 75.0..<100.0: | |
| return .pang | |
| case 100.0...: | |
| return .ggu | |
| default: | |
| return .mong | |
| } | |
| } | |
| var name: String { | |
| switch self { | |
| case .mong: return "몽롱체리" | |
| case .bbo: return "뽀득체리" | |
| case .chok: return "촉촉체리" | |
| case .pang: return "팡팡체리" | |
| case .ggu: return "꾸꾸체리" | |
| } | |
| } | |
| var cherryImage: Image { | |
| Image("cherry\(levelNumber)") | |
| } | |
| var progressImage: Image { | |
| Image("challenge_gaugebar_\(levelNumber)") | |
| } | |
| } |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`
around lines 10 - 52, CherryLevel uses rawValue in cherryImage but levelNumber
in progressImage; make them consistent by using the same identifier across both
computed properties (either rawValue or levelNumber). Update either cherryImage
or progressImage to reference the other symbol (prefer using the explicit
levelNumber computed property for clarity) so both Image initializers use the
same value source; adjust Image("cherry\(...)") and
Image("challenge_gaugebar_\(...)") accordingly in the CherryLevel enum's
cherryImage and progressImage.
| @State private var isChecked = false | ||
| private var progressRate: Double = 100 | ||
| private var cherryLevel: CherryLevel { | ||
| CherryLevel.from(progressRate: progressRate) | ||
| } | ||
| private var remainMissions: Int = 3 |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
하드코딩된 상태 값들을 ViewModel 또는 파라미터로 전환이 필요합니다.
progressRate, remainMissions 등이 현재 private var로 하드코딩되어 있어 동적으로 변경할 수 없습니다. 실제 서버 데이터 연동 시 ViewModel에서 주입받거나 View 초기화 파라미터로 받도록 변경이 필요합니다.
♻️ 권장 구조 예시
struct ChallengeProgressView: View {
-
- `@State` private var isChecked = false
- private var progressRate: Double = 100
- private var cherryLevel: CherryLevel {
- CherryLevel.from(progressRate: progressRate)
- }
- private var remainMissions: Int = 3
+ `@ObservedObject` var viewModel: ChallengeProgressViewModel
+
+ private var cherryLevel: CherryLevel {
+ CherryLevel.from(progressRate: viewModel.progressRate)
+ }🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`
around lines 56 - 61, The hardcoded view state (progressRate, remainMissions,
isChecked) must be moved out of the view and supplied by a ViewModel or
initializer: create a ChallengeProgressViewModel (ObservableObject) exposing
`@Published` properties progressRate: Double, remainMissions: Int, isChecked: Bool
(or expose Bindings if parent needs two‑way control), inject it into
ChallengeProgressView as `@ObservedObject` var viewModel (or add init parameters
for let progressRate: Double, let remainMissions: Int and Binding<Bool>
isChecked) and update the computed cherryLevel to call
CherryLevel.from(progressRate: viewModel.progressRate) (or from the init
property). Ensure all usages of progressRate, remainMissions, and isChecked
within ChallengeProgressView reference the viewModel properties or initializer
parameters so the UI reacts to external data changes.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift
Outdated
Show resolved
Hide resolved
| // @Published var routines: [RoutineType] = [] | ||
| @Published var routines: [RoutineType] = RoutineType.allCases | ||
|
|
||
| @Published var selectedRoutine: RoutineType? = nil |
There was a problem hiding this comment.
불필요한 optional nil 초기화 제거 권장.
SwiftLint 경고(redundant_optional_initialization)로 보고되고, 의미상 불필요합니다.
🧹 제안 수정
- `@Published` var selectedRoutine: RoutineType? = nil
+ `@Published` var selectedRoutine: RoutineType?🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 39-39: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/SelectRoutineViewModel.swift`
at line 39, Remove the redundant explicit nil initializer for the optional
published property: in SelectRoutineViewModel change the declaration of
selectedRoutine (type RoutineType?) to omit "= nil" so it reads just "@Published
var selectedRoutine: RoutineType?" to satisfy SwiftLint's
redundant_optional_initialization rule.
| private func moveNextAfterDelay() { | ||
| Task { | ||
| navigationTask = Task { | ||
| try? await Task.sleep(nanoseconds: 3_000_000_000) | ||
| guard !Task.isCancelled else { return } | ||
| await MainActor.run { | ||
| challengeCoordinator.push(.selectMission) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
중첩된 Task 구조로 인해 취소 로직이 제대로 동작하지 않습니다.
navigationTask가 외부 Task 블록 내부에서 비동기적으로 할당되기 때문에, onDisappear가 onAppear 직후에 호출되면 navigationTask가 아직 nil인 상태일 수 있습니다. 이 경우 navigationTask?.cancel()이 아무런 효과가 없어 원치 않는 네비게이션이 발생할 수 있습니다.
외부 Task 래퍼를 제거하고 navigationTask를 직접 할당해야 합니다.
🐛 수정 제안
private func moveNextAfterDelay() {
- Task {
- navigationTask = Task {
- try? await Task.sleep(nanoseconds: 3_000_000_000)
- guard !Task.isCancelled else { return }
- await MainActor.run {
- challengeCoordinator.push(.selectMission)
- }
+ navigationTask = Task {
+ try? await Task.sleep(nanoseconds: 3_000_000_000)
+ guard !Task.isCancelled else { return }
+ await MainActor.run {
+ challengeCoordinator.push(.selectMission)
}
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private func moveNextAfterDelay() { | |
| Task { | |
| navigationTask = Task { | |
| try? await Task.sleep(nanoseconds: 3_000_000_000) | |
| guard !Task.isCancelled else { return } | |
| await MainActor.run { | |
| challengeCoordinator.push(.selectMission) | |
| } | |
| } | |
| } | |
| } | |
| private func moveNextAfterDelay() { | |
| navigationTask = Task { | |
| try? await Task.sleep(nanoseconds: 3_000_000_000) | |
| guard !Task.isCancelled else { return } | |
| await MainActor.run { | |
| challengeCoordinator.push(.selectMission) | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift`
around lines 42 - 52, The nested Task wrapper in moveNextAfterDelay prevents
navigationTask from being set immediately, breaking cancellation; remove the
outer Task and assign navigationTask directly (navigationTask = Task { ... }) so
cancellation from onDisappear (navigationTask?.cancel()) takes effect, keep the
sleep, guard !Task.isCancelled check, and await MainActor.run {
challengeCoordinator.push(.selectMission) } inside that single Task.
| #Preview { | ||
| SelectRoutineView() | ||
| } |
There was a problem hiding this comment.
프리뷰에서 EnvironmentObject 미주입으로 크래시 가능.
프리뷰 실행 시 challengeCoordinator/tabBarCoordinator가 없어서 실패합니다.
🧩 제안 수정
`#Preview` {
- SelectRoutineView()
+ SelectRoutineView()
+ .environmentObject(ChallengeCoordinator())
+ .environmentObject(TabBarCoordinator())
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #Preview { | |
| SelectRoutineView() | |
| } | |
| `#Preview` { | |
| SelectRoutineView() | |
| .environmentObject(ChallengeCoordinator()) | |
| .environmentObject(TabBarCoordinator()) | |
| } |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift`
around lines 82 - 84, Preview crashes because SelectRoutineView expects
EnvironmentObjects challengeCoordinator and tabBarCoordinator that aren’t
provided; update the Preview provider (the `#Preview` / Preview struct for
SelectRoutineView) to inject mock or default instances via
.environmentObject(...) for both challengeCoordinator and tabBarCoordinator so
the view can render in Xcode previews (reference SelectRoutineView,
challengeCoordinator, tabBarCoordinator, and the `#Preview` block).
| struct ChallengeProgressView: View { | ||
|
|
||
| @State private var isChecked = false | ||
| private var progressRate: Double = 100 |
There was a problem hiding this comment.
네네 맞아요! 확인용으로 일단 100으로 해놨습니당
| @State private var missions: [String] = [ | ||
| "진정 토너 + 세럼", | ||
| "진정 토너 + 세럼", | ||
| "선크림 3번 바르기", | ||
| "선크림 3번 바르기", | ||
| "선크림 3번 바르기", | ||
| "선크림 3번 바르기" | ||
| ] | ||
|
|
||
| @State private var selectedStates: [Bool] = Array(repeating: false, count: 6) |
There was a problem hiding this comment.
이 부분들은 나중에 다 뷰모델로 옮겨야할것 같아요!
There was a problem hiding this comment.
넵! 뷰모델 작성할 때 반영하겠습니당
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
뷰 구현 할때 var bod에 모두 다 쓰지 말고 extension으로 밑에 빼서 가독성을 올려보면 좋을 것 같아요
struct view: some View {
var body: some View {
예시뷰1
예시 뷰2
}
extension 뷰 {
private var 예시뷰: some view {
}요런식으로요!!
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift
Show resolved
Hide resolved
| VStack(alignment: .leading, spacing: 4.adjustedH) { | ||
| TypographyText("챌린지 기간 동안", | ||
| style: .title1_sb_18, | ||
| color: .gray1000 | ||
| ) | ||
| TypographyText("진행할 미션을 선택해주세요.", | ||
| style: .title1_sb_18, | ||
| color: .gray1000 | ||
| ) | ||
| TypographyText("복수 선택이 가능해요.", | ||
| style: .body1_r_14, | ||
| color: .gray700 |
There was a problem hiding this comment.
피그마 확인해보니까 챌린지기간동안 미션 선택해주세요 이 부분은 spacing 4가 들어가지는 않는 것 같아요!
\n으로 개행해서 프레임값 주고, 복수선택이 가능해요 이것만 spacing 주면 될 것 같아용
| #Preview { | ||
| SelectRoutineView() | ||
| } |
| Image(isSelected ? .radiobtnSelected : .radiobtnDefault) | ||
| .padding(.leading, 14.adjustedW) | ||
| .padding(.vertical, 14.adjustedH) | ||
| TypographyText(missionText, style: .body1_r_14, color: isSelected ? .gray800 : .gray700) |
| @Binding var isSelected: Bool | ||
|
|
||
| var body: some View { | ||
| VStack(alignment: .leading) { |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift`:
- Around line 79-81: The Spacer() call in SelectMissionView.swift has a trailing
.padding(.bottom, 72.adjustedH) which has no effect; remove the unnecessary
.padding from the Spacer() and either (a) apply the padding to the surrounding
container (e.g., the parent VStack) or (b) replace with a fixed-height spacer
using .frame(height:) if you intended a concrete gap; update the Spacer() usage
accordingly so layout behaves as expected.
♻️ Duplicate comments (5)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift (3)
63-72:selectedStates배열이missions와 동기화되지 않았습니다.
SelectMissionView에서는onAppear와onChange를 통해selectedStates를missions.count에 맞게 동기화하도록 수정되었지만, 이 뷰에서는 여전히 하드코딩된count: 6을 사용하고 있습니다. 동일한 패턴을 적용해주세요.♻️ SelectMissionView와 동일한 동기화 패턴 적용
`@State` private var selectedStates: [Bool] = Array(repeating: false, count: 6)body 끝에 추가:
.onAppear { selectedStates = Array(repeating: false, count: missions.count) } .onChange(of: missions) { newMissions in selectedStates = Array(repeating: false, count: newMissions.count) }
109-109: 진행률 표시 형식 개선이 필요합니다.
progressRate가Double타입이므로 "75.5%"처럼 소수점이 표시될 수 있습니다.-TypographyText("챌린지 달성률 \(progressRate)%", style: .body1_m_14, color: .gray900) +TypographyText("챌린지 달성률 \(Int(progressRate))%", style: .body1_m_14, color: .gray900)
145-147: 버튼 액션이 비어있습니다."오늘 미션 종료하기" 버튼의 액션이 구현되지 않았습니다. 추후 구현이 필요하다면 TODO 주석을 추가해주세요.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift (1)
43-52: 중첩된 Task 구조가 여전히 존재합니다.이전 리뷰에서 지적된 중첩 Task 문제가 해결되지 않았습니다.
navigationTask가 외부Task블록 내부에서 비동기적으로 할당되므로,onDisappear가onAppear직후에 호출되면navigationTask가 아직nil인 상태일 수 있습니다. 이 경우 취소 로직이 동작하지 않아 원치 않는 네비게이션이 발생합니다.🐛 외부 Task 래퍼 제거 제안
private func moveNextAfterDelay() { - Task { - navigationTask = Task { - try? await Task.sleep(nanoseconds: 3_000_000_000) - guard !Task.isCancelled else { return } - await MainActor.run { - challengeCoordinator.push(.selectMission) - } + navigationTask = Task { + try? await Task.sleep(nanoseconds: 3_000_000_000) + guard !Task.isCancelled else { return } + await MainActor.run { + challengeCoordinator.push(.selectMission) } } }Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift (1)
71-73:Spacer()에 적용된.padding()은 효과가 없습니다.
Spacer()는 빈 공간을 채우는 역할만 하므로.padding(.horizontal)을 적용해도 레이아웃에 영향을 주지 않습니다.♻️ 불필요한 padding 제거
Spacer() .frame(height: 48.adjustedH) -.padding(.horizontal, 33.adjustedW)
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/illustration_challenge_start.imageset/illustration_challenge_start.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/illustration_challenge_start.imageset/Contents.jsonCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/StartChallengeView.swiftCherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swift
🧰 Additional context used
🧬 Code graph analysis (6)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (1)
body(9-17)Cherrish-iOS/Cherrish-iOS/Presentation/Coordinator/AppCoordinatorProtocol.swift (2)
pop(22-24)popToRoot(26-28)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (2)
gray1000(49-51)gray700(37-39)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift (4)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (1)
body(9-17)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+.swift (1)
highlight(11-20)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (2)
gray800(41-43)gray600(33-35)Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinator.swift (1)
push(24-26)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/StartChallengeView.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinator.swift (1)
push(24-26)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swift (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (4)
gray800(41-43)gray700(37-39)red100(65-67)gray0(9-11)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift (2)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (2)
body(9-17)cherrishShadow(21-23)Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Color.swift (7)
gray1000(49-51)gray700(37-39)gray900(45-47)gray800(41-43)gray300(21-23)red200(69-71)gray0(9-11)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift (4)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+Shadow.swift (1)
body(9-17)Cherrish-iOS/Cherrish-iOS/Presentation/Coordinator/AppCoordinatorProtocol.swift (2)
pop(22-24)popToRoot(26-28)Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinator.swift (1)
push(24-26)Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/SelectRoutineViewModel.swift (1)
select(45-47)
🔇 Additional comments (15)
Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/illustration_challenge_start.imageset/Contents.json (1)
1-12: LGTM!표준 Xcode 에셋 카탈로그 JSON 구조입니다. SVG 이미지 참조가 올바르게 설정되어 있습니다.
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/MissionCard.swift (1)
14-33: LGTM!MissionCard 컴포넌트가 잘 구현되었습니다. 이미지와 텍스트 사이의 패딩(Line 21)이 추가되어 이전 리뷰 피드백이 반영되었습니다.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/LoadingView.swift (1)
14-41: UI 레이아웃 구현이 잘 되어 있습니다.로딩 화면의 레이아웃과 컴포넌트 배치가 적절합니다.
onDisappear에서 Task 취소 처리를 추가한 점이 좋습니다.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift (2)
10-52: CherryLevel enum 구현이 적절합니다.진행률 기반 레벨 매핑 로직이 명확하고, PR 목표에 명시된 범위(0-25-50-75-100)가 정확하게 구현되었습니다.
76-163: UI 레이아웃 구현이 잘 되어 있습니다.ScrollView 기반의 레이아웃 구조와 프로젝트의 디자인 시스템 컴포넌트(
cherrishShadow(),TypographyText, color extensions) 활용이 적절합니다.cherryLevel.cherryImage를 사용하여 이전 리뷰 피드백이 반영되었습니다.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift (3)
84-89:selectedStates동기화 로직이 잘 추가되었습니다.
onAppear와onChange를 통해missions배열 크기 변경 시selectedStates를 자동으로 동기화하도록 수정되어 이전 리뷰 피드백이 반영되었습니다.
28-78: 전체적인 뷰 구현이 잘 되어 있습니다.미션 선택 화면의 레이아웃,
ForEach를 통한MissionCard바인딩,nextButtonState계산 로직이 적절하게 구현되었습니다. Coordinator 패턴을 통한 네비게이션 처리도 프로젝트 패턴과 일관성 있습니다.
87-89:onChangeAPI는 현재 배포 타겟에 적합합니다.프로젝트의 최소 배포 타겟이 iOS 16.6이므로 현재의
onChange(of:)클로저 시그니처는 올바릅니다. 이 구문은 iOS 16.6에서 표준이며 작동합니다. iOS 17 이상으로 최소 배포 타겟을 올린다면 새로운 API({ oldValue, newValue in }시그니처)로 마이그레이션할 것을 고려하세요.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/StartChallengeView.swift (2)
11-13: 탭바 연동 추가 반영 좋습니다.
시작 액션에서 탭바 숨김까지 처리해 흐름이 명확합니다.Also applies to: 41-42
27-31: 헤더 레이아웃/일러스트 교체 반영 OK.
패딩과 일러스트 변경이 의도대로 반영된 것으로 보입니다.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectRoutineView.swift (5)
13-20: 뷰모델 도입과 그리드 컬럼 구성이 깔끔합니다.
상태 분리와 레이아웃 정의가 명확해졌어요.
27-34: 탭바 복구 경로 확인 부탁드립니다.
현재 탭바 표시 복구가 버튼 액션에만 묶여 있으니, 스와이프 뒤로가기/프로그램matic pop 등 다른 경로가 없는지 확인해주세요.
49-55: LazyVGrid 기반 루틴 렌더링 LGTM.
반복 렌더링과 간격 설정이 자연스럽습니다.
60-66: 다음 버튼 상태를 뷰모델에서 가져오는 방식 좋습니다.
안정적으로 상태 흐름이 정리된 점이 좋아요.
71-79: 선택 칩 바인딩 로직 명확합니다.
선택 상태와 뷰모델 연동이 간단하고 읽기 쉬워졌습니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift`:
- Around line 86-88: The onChange handler on missions is redundant because
missions is a static array and selectedStates is already initialized in
onAppear; remove the .onChange(of: missions) { ... } block to eliminate
dead/duplicate code, or if you plan to update missions dynamically later, keep
it but update the closure to the iOS17 signature (use { oldValue, newValue in }
onChange) and ensure it resets selectedStates = Array(repeating: false, count:
newValue.count); refer to the onChange(of: missions), missions, selectedStates,
and onAppear symbols when applying the change.
- Around line 62-67: The ForEach(missions.indices...) + selectedStates[index]
access is unsafe if selectedStates is empty; either ensure selectedStates is
created with the same count as missions before the view renders (e.g., populate
selectedStates in onAppear or init so selectedStates.count == missions.count) or
refactor to pair mission text and selection into a single model (e.g., struct
Mission { id, text, isSelected } and use ForEach(missions) with
MissionCard(missionText: mission.text, isSelected:
$missions[index].isSelected)). Alternatively, use safe access inside the ForEach
(e.g., iterate Array(missions.enumerated()) and bind isSelected via a Binding
that checks selectedStates.indices.contains(index) before get/set) so you never
index out of range when rendering MissionCard.
- Around line 75-77: 선택된 미션 데이터(selectedStates in SelectMissionView)가 다음 화면으로
전달되지 않으니, SelectMissionView에서 challengeCoordinator.push(.challengeProgress) 호출 시
선택값을 전달하도록 수정하세요: 방법 A) ChallengeRoute.enum의 challengeProgress 케이스에 연관값(예:
[SelectedState] 또는 [Mission])을 추가하고 push 메서드 시그니처를 해당 연관값을 받도록 변경한 뒤
SelectMissionView의 CherrishButton 클릭에서
challengeCoordinator.push(.challengeProgress(selectedStates))로 호출하도록 업데이트하세요; 또는
방법 B) ChallengeCoordinator 또는 공유 ViewModel(singleton 혹은 `@EnvironmentObject`)으로
selectedStates를 저장하는 프로퍼티를 추가하고 SelectMissionView에서 그 프로퍼티를 설정한 다음 기존
challengeCoordinator.push(.challengeProgress)로 화면을 전환해 다음 화면에서
coordinator/ViewModel의 해당 프로퍼티를 읽어 사용하도록 구현하세요.
♻️ Duplicate comments (2)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift (2)
71-73:Spacer()에 적용된.padding()은 효과가 없습니다.이전 리뷰에서 지적된 내용입니다.
Spacer()에.padding(.horizontal, 33.adjustedW)를 적용해도 레이아웃에 영향을 주지 않습니다.♻️ 불필요한 padding 제거
Spacer() .frame(height: 48.adjustedH) -.padding(.horizontal, 33.adjustedW)
22-22:selectedStates초기화 방식 개선을 권장합니다.
onAppear에서 재초기화하는 방식으로 해결했지만, 초기값을 빈 배열[]로 설정하면 더 안전합니다. 현재 하드코딩된count: 6은missions배열과 불일치할 가능성이 있습니다.♻️ 빈 배열로 초기화 제안
-@State private var selectedStates: [Bool] = Array(repeating: false, count: 6) +@State private var selectedStates: [Bool] = []
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| ForEach(missions.indices, id: \.self) { index in | ||
| MissionCard( | ||
| missionText: missions[index], | ||
| isSelected: $selectedStates[index] | ||
| ) | ||
| } |
There was a problem hiding this comment.
배열 인덱스 접근 시 안전성을 고려해주세요.
selectedStates를 빈 배열로 초기화하는 경우, onAppear 실행 전에 ForEach가 렌더링되면 인덱스 범위 초과로 크래시가 발생할 수 있습니다.
이를 방지하려면 ForEach 내에서 인덱스 유효성을 검사하거나, 미션과 선택 상태를 하나의 구조체로 통합하는 것이 좋습니다.
♻️ 안전한 접근 방식 제안
VStack(spacing: 10.adjustedH) {
- ForEach(missions.indices, id: \.self) { index in
- MissionCard(
- missionText: missions[index],
- isSelected: $selectedStates[index]
- )
+ ForEach(Array(zip(missions.indices, missions)), id: \.0) { index, mission in
+ if index < selectedStates.count {
+ MissionCard(
+ missionText: mission,
+ isSelected: $selectedStates[index]
+ )
+ }
}
}또는 더 나은 접근법:
struct Mission: Identifiable {
let id = UUID()
let text: String
var isSelected: Bool = false
}
`@State` private var missions: [Mission] = [
Mission(text: "진정 토너 + 세럼"),
// ...
]🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift`
around lines 62 - 67, The ForEach(missions.indices...) + selectedStates[index]
access is unsafe if selectedStates is empty; either ensure selectedStates is
created with the same count as missions before the view renders (e.g., populate
selectedStates in onAppear or init so selectedStates.count == missions.count) or
refactor to pair mission text and selection into a single model (e.g., struct
Mission { id, text, isSelected } and use ForEach(missions) with
MissionCard(missionText: mission.text, isSelected:
$missions[index].isSelected)). Alternatively, use safe access inside the ForEach
(e.g., iterate Array(missions.enumerated()) and bind isSelected via a Binding
that checks selectedStates.indices.contains(index) before get/set) so you never
index out of range when rendering MissionCard.
| CherrishButton(title: "플래너에 추가하기", type: .next, state: .constant(nextButtonState)){ | ||
| challengeCoordinator.push(.challengeProgress) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# ChallengeCoordinator 열거형과 push 메서드 확인
find . -type f -name "*.swift" | xargs grep -l "ChallengeCoordinator" | head -5Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 532
🏁 Script executed:
# SelectMissionView 파일에서 선택된 미션과 상태 확인
cat -n Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift | head -100Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 3740
🏁 Script executed:
# ChallengeCoordinator 정의 찾기
rg -n "enum ChallengeCoordinator|struct ChallengeCoordinator|class ChallengeCoordinator" -A 20 --type swiftRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 4606
🏁 Script executed:
# ChallengeRoute 열거형 정의 찾기
rg -n "enum ChallengeRoute" -A 20 --type swiftRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2344
🏁 Script executed:
# ChallengeCoordinatorView에서 challengeProgress 케이스 처리 확인
cat -n Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ChallengeCoordinatorView.swift | head -80Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2439
🏁 Script executed:
# SelectMissionView에서 ViewModel 관련 부분 확인
rg -n "@StateObject|@ObservedObject|ViewModel" Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift --type swiftRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 52
선택된 미션 데이터가 전달되지 않고 있습니다.
현재 SelectMissionView에서 선택한 미션 정보(selectedStates)가 로컬 @State 변수로만 관리되고 있으며, challengeCoordinator.push(.challengeProgress)를 통해 다음 화면으로 전달되지 않습니다. ChallengeRoute의 challengeProgress 케이스에는 연관값이 없고, push 메서드도 단순히 경로만 전달합니다.
선택된 미션 목록을 challengeProgress 화면에서 사용해야 한다면:
ChallengeRoute.challengeProgress에 선택된 미션을 연관값으로 추가하거나ChallengeCoordinator또는 공유 ViewModel에 선택 상태를 저장한 후 다음 화면에서 접근하도록 수정해주세요.
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift`
around lines 75 - 77, 선택된 미션 데이터(selectedStates in SelectMissionView)가 다음 화면으로
전달되지 않으니, SelectMissionView에서 challengeCoordinator.push(.challengeProgress) 호출 시
선택값을 전달하도록 수정하세요: 방법 A) ChallengeRoute.enum의 challengeProgress 케이스에 연관값(예:
[SelectedState] 또는 [Mission])을 추가하고 push 메서드 시그니처를 해당 연관값을 받도록 변경한 뒤
SelectMissionView의 CherrishButton 클릭에서
challengeCoordinator.push(.challengeProgress(selectedStates))로 호출하도록 업데이트하세요; 또는
방법 B) ChallengeCoordinator 또는 공유 ViewModel(singleton 혹은 `@EnvironmentObject`)으로
selectedStates를 저장하는 프로퍼티를 추가하고 SelectMissionView에서 그 프로퍼티를 설정한 다음 기존
challengeCoordinator.push(.challengeProgress)로 화면을 전환해 다음 화면에서
coordinator/ViewModel의 해당 프로퍼티를 읽어 사용하도록 구현하세요.
| .onChange(of: missions) { newMissions in | ||
| selectedStates = Array(repeating: false, count: newMissions.count) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check iOS deployment target from project files
find . -name "*.pbxproj" -o -name "project.yml" -o -name "*.xcconfig" | head -10Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 123
🏁 Script executed:
# Check the actual iOS deployment target
fd -type f -name "*.pbxproj" | head -1 | xargs cat | grep -i "IPHONEOS_DEPLOYMENT_TARGET" | head -10Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 240
🏁 Script executed:
# Examine the SelectMissionView.swift file and lines around 86-88
fd -type f -name "SelectMissionView.swift" | xargs catRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 240
🏁 Script executed:
# Extract iOS deployment target from pbxproj
cat ./Cherrish-iOS/Cherrish-iOS.xcodeproj/project.pbxproj | grep -i "IPHONEOS_DEPLOYMENT_TARGET" | head -5Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 225
🏁 Script executed:
# Find and read SelectMissionView.swift
find . -name "SelectMissionView.swift" -exec cat {} \;Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 3103
onChange 핸들러는 현재 실행되지 않는 불필요한 코드입니다.
missions는 정적인 배열로 초기화되어 이후 수정되지 않으므로, onChange 블록이 실행될 수 없습니다. 추가로 onAppear에서 이미 동일한 selectedStates 초기화를 수행하고 있어 코드가 중복됩니다.
향후 서버에서 미션 데이터를 받아와 missions를 동적으로 업데이트할 계획이 있다면 유지하고, 아니라면 제거를 권장합니다. 만약 유지한다면, iOS 17 이상 타겟 시 새로운 API 문법({ oldValue, newValue in })으로 업데이트해야 합니다.
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/SelectMissionView.swift`
around lines 86 - 88, The onChange handler on missions is redundant because
missions is a static array and selectedStates is already initialized in
onAppear; remove the .onChange(of: missions) { ... } block to eliminate
dead/duplicate code, or if you plan to update missions dynamically later, keep
it but update the closure to the iOS17 signature (use { oldValue, newValue in }
onChange) and ensure it resets selectedStates = Array(repeating: false, count:
newValue.count); refer to the onChange(of: missions), missions, selectedStates,
and onAppear symbols when applying the change.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry2.imageset/Contents.json`:
- Around line 1-12: The Contents.json in cherry2.imageset references cherry1.svg
which mismatches the folder name; rename the referenced asset to match the set
(either rename the actual file cherry1.svg → cherry2.svg and update the
"filename" field in cherry2.imageset/Contents.json to "cherry2.svg"), or if the
folder should be cherry1.imageset instead, rename the folder to cherry1.imageset
and ensure its Contents.json still points to "cherry1.svg"; also scan other sets
(e.g., cherry1.imageset that references cherry0.svg) for similar mismatches and
apply the same consistent renaming or document the intended naming convention.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`:
- Around line 10-39: The CherryLevel enum currently omits the chok case and
mis-maps progress ranges; update the enum to include case chok with appropriate
raw ordering (e.g., mong=1, bbo=2, chok=3, pang=4, ggu=5), then change
CherryLevel.from(progressRate:) to map 0.0..<25.0 -> .mong, 25.0..<50.0 -> .bbo,
50.0..<75.0 -> .chok, 75.0..<100.0 -> .pang, and progressRate >= 100.0 -> .ggu
(ensure >=100 goes to .ggu), and add the display string for .chok in the name
computed property; keep a sensible default (e.g., .mong) for unexpected values.
♻️ Duplicate comments (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift (3)
59-68:missions와selectedStates동기화 이슈 재확인 필요합니다.
이미 이전 커밋에서 지적된 항목과 동일합니다. 배열 길이 불일치 시 인덱스 오류가 납니다.
117-117: 달성률 표시가 소수로 노출될 수 있습니다.
이전 리뷰에서 언급된 사항과 동일합니다. 정수 포맷팅을 적용해 주세요.
158-160: 버튼 액션이 비어 있습니다.
이전 리뷰에서 지적된 항목과 동일합니다. 구현 또는 TODO 주석이 필요합니다.
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "cherry1.svg", | ||
| "idiom" : "universal" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check actual SVG files in cherry2.imageset folder
echo "=== Files in cherry2.imageset ==="
ls -la Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry2.imageset/
echo ""
echo "=== SVG files in cherry2.imageset ==="
find Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry2.imageset -type f -name "*.svg"
echo ""
echo "=== All SVG files in ChallengeImage folder ==="
find Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage -type f -name "*.svg"
echo ""
echo "=== cherry1.imageset structure for comparison ==="
ls -la Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry1.imageset/Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2147
cherry2.imageset 폴더명과 참조 파일명 불일치
cherry2.imageset 폴더에서 cherry1.svg 파일을 참조하고 있습니다. 폴더명과 파일명이 일치하지 않으며, cherry1.imageset에서도 동일한 패턴(cherry1 → cherry0.svg)이 있습니다.
일관성을 위해 다음 중 하나를 선택해 주세요:
cherry1.svg파일을cherry2.svg로 이름 변경하고 Contents.json 업데이트- 또는 의도된 네이밍 구조라면 문서화
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Assets.xcassets/ChallengeImage/cherry2.imageset/Contents.json`
around lines 1 - 12, The Contents.json in cherry2.imageset references
cherry1.svg which mismatches the folder name; rename the referenced asset to
match the set (either rename the actual file cherry1.svg → cherry2.svg and
update the "filename" field in cherry2.imageset/Contents.json to "cherry2.svg"),
or if the folder should be cherry1.imageset instead, rename the folder to
cherry1.imageset and ensure its Contents.json still points to "cherry1.svg";
also scan other sets (e.g., cherry1.imageset that references cherry0.svg) for
similar mismatches and apply the same consistent renaming or document the
intended naming convention.
| enum CherryLevel: Int { | ||
| case mong = 1 | ||
| case bbo | ||
| case pang | ||
| case ggu | ||
|
|
||
| var levelNumber: Int { rawValue } | ||
|
|
||
| static func from(progressRate: Double) -> CherryLevel { | ||
| switch progressRate { | ||
| case 0.0..<25.0: | ||
| return .mong | ||
| case 25.0..<50.0: | ||
| return .bbo | ||
| case 50.0..<75.0: | ||
| return .pang | ||
| case 75.0...100.0: | ||
| return .ggu | ||
| default: | ||
| return .mong | ||
| } | ||
| } | ||
|
|
||
| var name: String { | ||
| switch self { | ||
| case .mong: return "몽롱체리" | ||
| case .bbo: return "뽀득체리" | ||
| case .pang: return "팡팡체리" | ||
| case .ggu: return "꾸꾸체리" | ||
| } |
There was a problem hiding this comment.
PR 요구사항(5단계)과 레벨 매핑이 불일치합니다.
현재 chok 단계가 누락되어 있고, 75–100 구간이 ggu로 매핑되며 100 초과가 mong로 떨어집니다. 요구사항(0–25 mong, 25–50 bbo, 50–75 chok, 75–100 pang, 100+ ggu)에 맞게 수정이 필요합니다.
🛠️ 제안 수정안
enum CherryLevel: Int {
case mong = 1
case bbo
+ case chok
case pang
case ggu
@@
static func from(progressRate: Double) -> CherryLevel {
switch progressRate {
case 0.0..<25.0:
return .mong
case 25.0..<50.0:
return .bbo
case 50.0..<75.0:
- return .pang
- case 75.0...100.0:
- return .ggu
+ return .chok
+ case 75.0..<100.0:
+ return .pang
default:
- return .mong
+ return .ggu
}
}
@@
var name: String {
switch self {
case .mong: return "몽롱체리"
case .bbo: return "뽀득체리"
+ case .chok: return "촉촉체리"
case .pang: return "팡팡체리"
case .ggu: return "꾸꾸체리"
}
}
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift`
around lines 10 - 39, The CherryLevel enum currently omits the chok case and
mis-maps progress ranges; update the enum to include case chok with appropriate
raw ordering (e.g., mong=1, bbo=2, chok=3, pang=4, ggu=5), then change
CherryLevel.from(progressRate:) to map 0.0..<25.0 -> .mong, 25.0..<50.0 -> .bbo,
50.0..<75.0 -> .chok, 75.0..<100.0 -> .pang, and progressRate >= 100.0 -> .ggu
(ensure >=100 goes to .ggu), and add the display string for .chok in the name
computed property; keep a sensible default (e.g., .mong) for unexpected values.
🔗 연결된 이슈
📄 작업 내용
💻 주요 코드 설명
코드 설명할 파일 이름 (ex: ChallengeProgressView)👀 기타 더 이야기해볼 점
제발 완만히 어푸 받을 수 있길..!!! 뷰 마감 하고시퍼요 엉엉슨..