Conversation
📝 WalkthroughWalkthrough다운타임 모달 뷰를 구성하는 여러 컴포넌트를 연결하고 동적 상태 관리를 추가합니다. DownTimeSettingView에서 선택된 치료 항목을 관리하고, DowntimeBottomSheetView에서 다운타임 기간을 계산하여 표시합니다. Changes
Sequence DiagramsequenceDiagram
actor User
participant NTV as NoTreatmentView
participant DTSV as DownTimeSettingView
participant DBSV as DowntimeBottomSheetView
participant TVM as TreatmentViewModel
User->>NTV: 다운타임 설정 선택
NTV->>DTSV: treatments, today, setday 전달
User->>DTSV: 치료 항목 선택
DTSV->>DTSV: selectedTreatment 상태 업데이트
DTSV->>DBSV: treatment, today, setday로 Sheet 표시
DBSV->>DBSV: onAppear에서 상태 초기화<br/>(betweenDays, rate 계산)
User->>DBSV: 다운타임 기간 조정
DBSV->>DBSV: onChange로 rate 재계산
User->>DBSV: 확인 버튼 클릭
DBSV->>NTV: 모달 종료
NTV->>TVM: 선택된 다운타임 저장
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ 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: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishPicker.swift (1)
62-84: 배경색 초기화가 매 행 렌더링마다 반복됩니다
makeUIView에서도 동일 처리가 있어 중복 호출이 됩니다. 한 번만 수행되도록 가드하면 비용을 줄일 수 있습니다.♻️ 제안 변경
class Coordinator: NSObject, UIPickerViewDelegate, UIPickerViewDataSource { var parent: PickerViewRepresentable + private var didClearBackground = false init(_ parent: PickerViewRepresentable) { self.parent = parent } @@ func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - pickerView.subviews.forEach { subview in - subview.backgroundColor = .clear - } + if !didClearBackground { + pickerView.subviews.forEach { $0.backgroundColor = .clear } + didClearBackground = true + } let label = (view as? UILabel) ?? UILabel() label.text = "\(parent.range.lowerBound + row)"Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift (1)
13-33: 시트 표시는toggle()대신 명시적으로true설정을 권장의도는 “열기”이므로
isShowSheet = true가 더 안전하고 idempotent 합니다.♻️ 제안 변경
- TreatmentRowView(displayMode: .completeBoxView, treatmentEntity: treatment, isSelected: .constant(false), isCompleted: .constant(false), action: { isShowSheet.toggle() }) + TreatmentRowView(displayMode: .completeBoxView, treatmentEntity: treatment, isSelected: .constant(false), isCompleted: .constant(false), action: { isShowSheet = true })
🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift`:
- Line 19: Root view was changed to TreatmentView, bypassing AppCoordinatorView
(which handles SplashView, OnboardingCoordinatorView, and AppState routing);
restore AppCoordinatorView as the app entry point by replacing the
TreatmentView(...) instantiation in Cherrish_iOSApp (Cherrish_iOSApp.swift) with
AppCoordinatorView() (or initialize AppCoordinatorView with the same
dependencies TreatmentViewModel required if intentional), and if the change is
deliberate, add a brief comment documenting why AppCoordinatorView was removed
and how splash/onboarding/state flows are now handled.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift`:
- Around line 54-65: In DowntimeBottomSheetView replace the hardcoded
conditional in the TypographyText call (currently using `true ? ... : ...`) with
the real runtime boolean that determines whether the downtime is within allowed
range (e.g., a view state or computed property such as `isDowntimeWithinLimit`,
`viewModel.isDowntimeValid`, or a local computed like `isDowntimeValid()`), so
the correct message (the first or the else string) is shown based on actual
logic; update any surrounding code to compute that boolean from the
downtime/target-date inputs and pass it into the TypographyText expression
instead of the constant `true`.
- Around line 142-154: The GeometryReader-based width calc in buttonView
currently ignores the external .padding(.horizontal, 24.adjustedW); move the
horizontal padding inside the GeometryReader (apply it to the HStack) and change
the width calculations for the two CherrishButton frames to use (geo.size.width
- 48.adjustedW) as the available width (e.g. (geo.size.width - 48.adjustedW) *
2/3 - 2 and (geo.size.width - 48.adjustedW) * 1/3 - 2) so the buttons account
for both side paddings and avoid overflow.
| var body: some Scene { | ||
| WindowGroup { | ||
| AppCoordinatorView() | ||
| TreatmentView(viewModel: TreatmentViewModel()) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's explore the repository structure to understand the app layout
find . -type f -name "*.swift" | grep -E "(App|Cherrish)" | head -20Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1310
🏁 Script executed:
# Let's read the specific file mentioned in the review
cat -n "Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 564
🏁 Script executed:
# Search for AppCoordinatorView to understand what it does
rg "AppCoordinatorView" --type swift -A 10 -B 2Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2033
🏁 Script executed:
# Search for TreatmentView and its implementation
rg "struct TreatmentView" --type swift -A 10Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1561
🏁 Script executed:
# Look for onboarding, login, or authentication related code
rg "(onboarding|login|authentication|auth|coordinator)" --type swift -i | head -30Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 3907
🏁 Script executed:
# Read the AppCoordinator to understand the full flow
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Coordinator/AppCoordinator.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 838
🏁 Script executed:
# Check if there are any comments or TODOs about this change in recent commits
git log --oneline -20 --all -- "Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 254
🏁 Script executed:
# Check the specific hotfix commit to understand what was changed
git show ac7bd58 --statRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 331
🏁 Script executed:
# Look at the full diff of that hotfix to see what was replaced
git show ac7bd58Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 713
🏁 Script executed:
# Check the most recent commit to see what changed
git show 27a7246Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 50386
🏁 Script executed:
# Let's see the history more clearly with more context
git log -p --max-count=5 -- "Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 52
앱 진입점이 AppCoordinatorView에서 TreatmentView로 변경됨
현재 코드에서 루트 뷰가 TreatmentView로 설정되어 있어 스플래시 화면, 온보딩, 앱 상태 관리를 담당하는 AppCoordinatorView가 완전히 우회됩니다. 이로 인해 다음 흐름이 모두 건너뜁니다:
- SplashView (앱 초기화)
- OnboardingCoordinatorView (온보딩 플로우)
- AppState 기반 라우팅 (splash → onboarding → home)
TreatmentView는 기능별 뷰이지 앱 진입점이 아닙니다. AppCoordinatorView를 복구하거나 의도된 변경이라면 그 사유를 명시해주세요.
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/App/Cherrish_iOSApp.swift` at line 19, Root view
was changed to TreatmentView, bypassing AppCoordinatorView (which handles
SplashView, OnboardingCoordinatorView, and AppState routing); restore
AppCoordinatorView as the app entry point by replacing the TreatmentView(...)
instantiation in Cherrish_iOSApp (Cherrish_iOSApp.swift) with
AppCoordinatorView() (or initialize AppCoordinatorView with the same
dependencies TreatmentViewModel required if intentional), and if the change is
deliberate, add a brief comment documenting why AppCoordinatorView was removed
and how splash/onboarding/state flows are now handled.
...herrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift
Outdated
Show resolved
Hide resolved
| private var buttonView: some View { | ||
| GeometryReader { geo in | ||
| HStack(spacing: 4.adjustedW) { | ||
| CherrishButton(title: "다운타임 없이 일정 추가", type: .addEvent, state: .constant(.normal), leadingIcon: .none, trailingIcon: .none, action: {}) | ||
| .frame(width: geo.size.width * 2/3 - 2) | ||
|
|
||
| CherrishButton(title: "확인", type: .small, state: .constant(.normal), leadingIcon: .none, trailingIcon: .none, action: {}) | ||
| .frame(width: geo.size.width * 1/3 - 2) | ||
|
|
||
| } | ||
| } | ||
| .padding(.horizontal, 24.adjustedW) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "DowntimeBottomSheetView.swift" -type fRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 178
🏁 Script executed:
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift" | sed -n '130,160p'Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1143
🏁 Script executed:
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift" | grep -A 20 "buttonView"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1650
🏁 Script executed:
wc -l "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 180
🏁 Script executed:
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift" | sed -n '1,50p'Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1650
GeometryReader 폭 계산이 외부 패딩을 고려하지 않아 오버플로우 위험
geo.size.width를 기반으로 버튼 너비를 계산한 뒤 GeometryReader 외부에 패딩을 적용하면, 패딩까지 포함된 전체 프레임이 할당된 공간을 초과할 수 있습니다. 패딩을 내부로 이동하고 폭 계산에서 양쪽 패딩(48.adjustedW)을 빼서 수정해주세요.
🛠️ 제안 변경
private var buttonView: some View {
GeometryReader { geo in
+ let total = geo.size.width - 48.adjustedW
HStack(spacing: 4.adjustedW) {
CherrishButton(title: "다운타임 없이 일정 추가", type: .addEvent, state: .constant(.normal), leadingIcon: .none, trailingIcon: .none, action: {})
- .frame(width: geo.size.width * 2/3 - 2)
+ .frame(width: total * 2/3 - 2)
CherrishButton(title: "확인", type: .small, state: .constant(.normal), leadingIcon: .none, trailingIcon: .none, action: {})
- .frame(width: geo.size.width * 1/3 - 2)
+ .frame(width: total * 1/3 - 2)
}
+ .padding(.horizontal, 24.adjustedW)
}
- .padding(.horizontal, 24.adjustedW)
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift`
around lines 142 - 154, The GeometryReader-based width calc in buttonView
currently ignores the external .padding(.horizontal, 24.adjustedW); move the
horizontal padding inside the GeometryReader (apply it to the HStack) and change
the width calculations for the two CherrishButton frames to use (geo.size.width
- 48.adjustedW) as the available width (e.g. (geo.size.width - 48.adjustedW) *
2/3 - 2 and (geo.size.width - 48.adjustedW) * 1/3 - 2) so the buttons account
for both side paddings and avoid overflow.
| var body: some Scene { | ||
| WindowGroup { | ||
| AppCoordinatorView() | ||
| TreatmentView(viewModel: TreatmentViewModel()) |
| .padding(.horizontal, 18.adjustedW) | ||
| .offset(y: -4.adjustedH) | ||
| .padding(.top, 12.adjustedH) | ||
| .padding(.bottom, 20.59.adjustedH) |
| VStack(spacing: 0) { | ||
| HStack(spacing: 0) { |
| CherrishButton(title: "다운타임 없이 일정 추가", type: .addEvent, state: .constant(.normal), leadingIcon: .none, trailingIcon: .none, action: {}) | ||
| .frame(width: geo.size.width * 2/3 - 2) | ||
|
|
||
| CherrishButton(title: "확인", type: .small, state: .constant(.normal), leadingIcon: .none, trailingIcon: .none, action: {}) | ||
| .frame(width: geo.size.width * 1/3 - 2) |
| ScrollView(.vertical, showsIndicators: false) { | ||
| ForEach(treatments, id: \.self) { treatment in | ||
| TreatmentRowView(displayMode: .completeBoxView, treatmentEntity: treatment, isSelected: .constant(false), isCompleted: .constant(false), action: {}) | ||
| TreatmentRowView(displayMode: .completeBoxView, treatmentEntity: treatment, isSelected: .constant(false), isCompleted: .constant(false), action: { isShowSheet.toggle() }) |
There was a problem hiding this comment.
Actionable comments posted: 6
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/Feature/Calendar/Treatment/NoTreatment/View/NoTreatmentView.swift (1)
97-109: 버튼에 중복 패딩이 적용됩니다.
CherrishButton에.padding(.horizontal, 25.adjustedW)(line 106)가 적용되고, 부모VStack에도.padding(.leading, 25.adjustedW)와.padding(.trailing, 24.adjustedW)(lines 108-109)가 적용됩니다. 이로 인해 버튼에 의도하지 않은 이중 패딩이 발생할 수 있습니다.
TreatmentView.swift의bottomView()와 비교했을 때, 해당 파일에는 VStack에 추가 패딩이 없습니다.🔧 제안: 중복 패딩 제거
.padding(.horizontal, 25.adjustedW) } - .padding(.leading, 25.adjustedW) - .padding(.trailing, 24.adjustedW) }
🤖 Fix all issues with AI agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/TreatmentEntity.swift`:
- Around line 10-11: TreatmentEntity currently uses var id: Self { self }, which
breaks SwiftUI identity when any property changes; change to a stable identifier
by adding a stored unique id (e.g., let uuid: UUID) and implement var id: UUID {
uuid } or, if name is guaranteed unique, implement var id: String { name } and
remove/adjust any uses expecting Self as id; update initializers to set uuid and
ensure Equatable/Hashable semantics rely on the stable id instead of whole-self
equality.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift`:
- Around line 62-66: The calculation for rate (in the view where rate,
selectedDowntime and betweenDays are used, including the onAppear and .onChange
blocks) can divide by zero when betweenDays == 0; change both places to guard
the denominator by checking if betweenDays > 0 and compute rate =
min(Double(selectedDowntime) / Double(betweenDays), 1.0) only then, otherwise
set rate = 0 (or 0.0), ensuring no infinite/NaN results when today == setday.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift`:
- Line 11: Remove the unnecessary explicit nil initialization on the Optional
`@State` property by changing the declaration of selectedTreatment; since Optional
types default to nil in Swift, update the `@State` var selectedTreatment:
TreatmentEntity? = nil to simply `@State` var selectedTreatment: TreatmentEntity?
to clean up the code and adhere to Swift conventions.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift`:
- Around line 67-81: The setday tuple passed into DownTimeSettingView is overly
verbose across multiple lines; simplify by constructing the tuple inline or by
creating a short local variable using viewModel.toInt for year/month/day and
passing that variable to DownTimeSettingView. Locate the DownTimeSettingView
invocation in TreatmentView.swift and replace the multi-line setday:
(viewModel.toInt(viewModel.year), viewModel.toInt(viewModel.month),
viewModel.toInt(viewModel.day)) with a single-line tuple or with a small let
setday = (year: viewModel.toInt(viewModel.year), month:
viewModel.toInt(viewModel.month), day: viewModel.toInt(viewModel.day)) and pass
setday to improve readability.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift`:
- Around line 28-36: The present `today` computed property and `toInt(_:)`
utility are duplicated in TreatmentViewModel (var today) and
NoTreatmentViewModel; extract them into shared extensions to remove duplication:
create a Date extension (e.g., Date.components or similar) that returns (year:
Int, month: Int, day: Int) and replace the `var today` in TreatmentViewModel and
NoTreatmentViewModel with Date().components, and create a String extension
(e.g., func toInt(default: Int = 0)) to replace the local `toInt(_:)`
implementations so both view models call the shared helpers instead.
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Int`+.swift:
- Around line 10-24: The daysBetween(_:fromYear:...) function is misplaced on
Int and returns 0 for invalid dates which confuses errors with real zero
differences; move the logic out of the Int extension into a Date-centric helper
(e.g. add a static Date.daysBetween(from:to:) or a DateHelper.type) and update
the function signature (daysBetween) to accept tuple or DateComponents inputs
and return an optional Int? so invalid date parsing returns nil; ensure you
locate and remove or deprecate the original Int.extension daysBetween and rewire
any callers to the new Date.daysBetween/DateHelper.daysBetween to preserve
behavior.
♻️ Duplicate comments (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift (1)
182-213: GeometryReader 폭 계산이 외부 패딩을 고려하지 않아 오버플로우 위험이전 리뷰에서 지적된 것처럼,
geo.size.width기반 버튼 너비 계산 후 외부에 패딩을 적용하면 전체 프레임이 할당된 공간을 초과할 수 있습니다.Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/ViewModel/NoTreatmentViewModel.swift (1)
35-43:TreatmentViewModel과 중복되는 코드입니다.
today와toInt(_:)가TreatmentViewModel.swift에도 동일하게 구현되어 있습니다. 공통 유틸리티로 추출하면 코드 중복을 줄일 수 있습니다.Also applies to: 53-55
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/View/NoTreatmentView.swift (1)
70-83:setday튜플 포맷팅이TreatmentView.swift와 동일하게 장황합니다.가독성 향상을 위해 간소화를 고려해 주세요 (이전 리뷰 참조).
| struct TreatmentEntity: Identifiable, Equatable, Hashable { | ||
| var id: Self { self } |
There was a problem hiding this comment.
Self를 id로 사용하면 속성 변경 시 SwiftUI가 아이템 동일성을 잃을 수 있습니다.
var id: Self { self }는 모든 속성이 동일해야 같은 아이템으로 인식됩니다. 속성이 변경되면 SwiftUI가 이를 새 아이템으로 간주하여 리스트 애니메이션이나 상태 유지에 문제가 발생할 수 있습니다.
안정적인 식별자(예: UUID 또는 고유한 name)를 사용하는 것이 권장됩니다.
🛠️ 제안된 수정
-struct TreatmentEntity: Identifiable, Equatable, Hashable {
- var id: Self { self }
+struct TreatmentEntity: Identifiable, Equatable, Hashable {
+ let id: UUID = UUID()
let name: String
let benefits: [String]
let downtimeMin: Int
let downtimeMax: Int
}또는 name이 고유하다면:
var id: String { name }🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Domain/Model/TreatmentEntity.swift` around lines 10
- 11, TreatmentEntity currently uses var id: Self { self }, which breaks SwiftUI
identity when any property changes; change to a stable identifier by adding a
stored unique id (e.g., let uuid: UUID) and implement var id: UUID { uuid } or,
if name is guaranteed unique, implement var id: String { name } and
remove/adjust any uses expecting Self as id; update initializers to set uuid and
ensure Equatable/Hashable semantics rely on the stable id instead of whole-self
equality.
| rate = min(Double(selectedDowntime) / Double(betweenDays), 1.0) | ||
| } | ||
| .onChange(of: selectedDowntime) { | ||
| rate = min(Double(selectedDowntime) / Double(betweenDays), 1.0) | ||
| } |
There was a problem hiding this comment.
betweenDays가 0일 때 0으로 나누기 오류가 발생합니다.
today와 setday가 동일한 날짜일 경우 betweenDays가 0이 되어 Double(selectedDowntime) / Double(betweenDays)에서 무한대(inf) 또는 NaN이 발생합니다.
🐛 제안된 수정
.onAppear {
selectedDowntime = treatment.downtimeMax
betweenDays = Int.daysBetween(
fromYear: setday.year,
fromMonth: setday.month,
fromDay: setday.day,
toYear: today.year,
toMonth: today.month,
toDay: today.day
)
- rate = min(Double(selectedDowntime) / Double(betweenDays), 1.0)
+ rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0
}
.onChange(of: selectedDowntime) {
- rate = min(Double(selectedDowntime) / Double(betweenDays), 1.0)
+ rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift`
around lines 62 - 66, The calculation for rate (in the view where rate,
selectedDowntime and betweenDays are used, including the onAppear and .onChange
blocks) can divide by zero when betweenDays == 0; change both places to guard
the denominator by checking if betweenDays > 0 and compute rate =
min(Double(selectedDowntime) / Double(betweenDays), 1.0) only then, otherwise
set rate = 0 (or 0.0), ensuring no infinite/NaN results when today == setday.
| import SwiftUI | ||
|
|
||
| struct DownTimeSettingView: View { | ||
| @State var selectedTreatment: TreatmentEntity? = nil |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Optional 변수를 nil로 초기화하는 것은 불필요합니다.
Swift에서 Optional 타입은 기본값이 nil이므로 명시적 초기화가 필요하지 않습니다.
♻️ 제안된 수정
- `@State` var selectedTreatment: TreatmentEntity? = nil
+ `@State` var selectedTreatment: TreatmentEntity?🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 11-11: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift`
at line 11, Remove the unnecessary explicit nil initialization on the Optional
`@State` property by changing the declaration of selectedTreatment; since Optional
types default to nil in Swift, update the `@State` var selectedTreatment:
TreatmentEntity? = nil to simply `@State` var selectedTreatment: TreatmentEntity?
to clean up the code and adhere to Swift conventions.
| DownTimeSettingView( | ||
| treatments: viewModel.selectedTreatments, | ||
| setday: ( | ||
| viewModel.toInt( | ||
| viewModel.year | ||
| ), | ||
| viewModel.toInt( | ||
| viewModel.month | ||
| ), | ||
| viewModel.toInt( | ||
| viewModel.day | ||
| ) | ||
| } | ||
| Spacer() | ||
| ), | ||
| today: viewModel.today | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
setday 튜플 포맷팅 간소화를 고려해 주세요.
현재 각 요소가 여러 줄에 걸쳐 있어 가독성이 떨어집니다.
✨ 간소화 제안
case .downTimeSetting:
DownTimeSettingView(
treatments: viewModel.selectedTreatments,
- setday: (
- viewModel.toInt(
- viewModel.year
- ),
- viewModel.toInt(
- viewModel.month
- ),
- viewModel.toInt(
- viewModel.day
- )
- ),
+ setday: (viewModel.toInt(viewModel.year),
+ viewModel.toInt(viewModel.month),
+ viewModel.toInt(viewModel.day)),
today: viewModel.today
)📝 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.
| DownTimeSettingView( | |
| treatments: viewModel.selectedTreatments, | |
| setday: ( | |
| viewModel.toInt( | |
| viewModel.year | |
| ), | |
| viewModel.toInt( | |
| viewModel.month | |
| ), | |
| viewModel.toInt( | |
| viewModel.day | |
| ) | |
| } | |
| Spacer() | |
| ), | |
| today: viewModel.today | |
| ) | |
| DownTimeSettingView( | |
| treatments: viewModel.selectedTreatments, | |
| setday: (viewModel.toInt(viewModel.year), | |
| viewModel.toInt(viewModel.month), | |
| viewModel.toInt(viewModel.day)), | |
| today: viewModel.today | |
| ) |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift`
around lines 67 - 81, The setday tuple passed into DownTimeSettingView is overly
verbose across multiple lines; simplify by constructing the tuple inline or by
creating a short local variable using viewModel.toInt for year/month/day and
passing that variable to DownTimeSettingView. Locate the DownTimeSettingView
invocation in TreatmentView.swift and replace the multi-line setday:
(viewModel.toInt(viewModel.year), viewModel.toInt(viewModel.month),
viewModel.toInt(viewModel.day)) with a single-line tuple or with a small let
setday = (year: viewModel.toInt(viewModel.year), month:
viewModel.toInt(viewModel.month), day: viewModel.toInt(viewModel.day)) and pass
setday to improve readability.
| var today: (year: Int, month: Int, day: Int) { | ||
| let calendar = Calendar.current | ||
| let now = Date() | ||
| return ( | ||
| calendar.component(.year, from: now), | ||
| calendar.component(.month, from: now), | ||
| calendar.component(.day, from: now) | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
today와 toInt(_:) 유틸리티가 NoTreatmentViewModel과 중복됩니다.
NoTreatmentViewModel.swift의 lines 35-43과 53-55에 동일한 구현이 존재합니다. 이는 DRY 원칙 위반으로, 추후 수정 시 두 곳을 동시에 변경해야 하는 유지보수 부담이 발생합니다.
공통 유틸리티를 별도 extension이나 공유 헬퍼로 추출하는 것을 권장합니다.
♻️ 제안: 공통 Extension으로 추출
예시 - Date+Components.swift:
extension Date {
var components: (year: Int, month: Int, day: Int) {
let calendar = Calendar.current
return (
calendar.component(.year, from: self),
calendar.component(.month, from: self),
calendar.component(.day, from: self)
)
}
}예시 - String+ToInt.swift:
extension String {
func toInt(default defaultValue: Int = 0) -> Int {
Int(self) ?? defaultValue
}
}이후 ViewModel에서:
var today: (year: Int, month: Int, day: Int) {
Date().components
}Also applies to: 61-63
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift`
around lines 28 - 36, The present `today` computed property and `toInt(_:)`
utility are duplicated in TreatmentViewModel (var today) and
NoTreatmentViewModel; extract them into shared extensions to remove duplication:
create a Date extension (e.g., Date.components or similar) that returns (year:
Int, month: Int, day: Int) and replace the `var today` in TreatmentViewModel and
NoTreatmentViewModel with Date().components, and create a String extension
(e.g., func toInt(default: Int = 0)) to replace the local `toInt(_:)`
implementations so both view models call the shared helpers instead.
| extension Int { | ||
| static func daysBetween( | ||
| fromYear: Int, fromMonth: Int, fromDay: Int, | ||
| toYear: Int, toMonth: Int, toDay: Int | ||
| ) -> Int { | ||
| let calendar = Calendar.current | ||
|
|
||
| guard let fromDate = calendar.date(from: DateComponents(year: fromYear, month: fromMonth, day: fromDay)), | ||
| let toDate = calendar.date(from: DateComponents(year: toYear, month: toMonth, day: toDay)) else { | ||
| return 0 | ||
| } | ||
|
|
||
| return abs(calendar.dateComponents([.day], from: fromDate, to: toDate).day ?? 0) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Int extension보다 Date 또는 별도 유틸리티 타입에 배치하는 것이 더 적절합니다.
daysBetween은 날짜 간 일수 차이를 계산하는 함수로, Int 타입과 의미적 연관성이 낮습니다. Date extension이나 DateHelper 같은 전용 유틸리티 타입에 배치하면 코드 검색성과 응집도가 향상됩니다.
또한, 유효하지 않은 날짜 입력 시 0을 반환하면 실제로 0일 차이인 경우와 구분이 불가능합니다.
♻️ 제안된 개선안
// DateHelper.swift 또는 Date+.swift로 이동 권장
extension Date {
static func daysBetween(
from: (year: Int, month: Int, day: Int),
to: (year: Int, month: Int, day: Int)
) -> Int? {
let calendar = Calendar.current
guard let fromDate = calendar.date(from: DateComponents(year: from.year, month: from.month, day: from.day)),
let toDate = calendar.date(from: DateComponents(year: to.year, month: to.month, day: to.day)) else {
return nil
}
return abs(calendar.dateComponents([.day], from: fromDate, to: toDate).day ?? 0)
}
}🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Int`+.swift around
lines 10 - 24, The daysBetween(_:fromYear:...) function is misplaced on Int and
returns 0 for invalid dates which confuses errors with real zero differences;
move the logic out of the Int extension into a Date-centric helper (e.g. add a
static Date.daysBetween(from:to:) or a DateHelper.type) and update the function
signature (daysBetween) to accept tuple or DateComponents inputs and return an
optional Int? so invalid date parsing returns nil; ensure you locate and remove
or deprecate the original Int.extension daysBetween and rewire any callers to
the new Date.daysBetween/DateHelper.daysBetween to preserve behavior.
sum130
left a comment
There was a problem hiding this comment.
수고했어용~~ 전체적으로 개행! 한번씩 쭉~ 정리해주면 더 좋을 것 같아요ㅎㅎ
| struct TreatmentEntity: Identifiable, Equatable, Hashable { | ||
| var id: Self { self } |
| .presentationDetents([.extraLarge]) | ||
| .presentationBackground(.gray0) | ||
| .presentationDragIndicator(.visible) |
| rate = min(Double(selectedDowntime) / Double(betweenDays), 1.0) | ||
| } | ||
| .onChange(of: selectedDowntime) { | ||
| rate = min(Double(selectedDowntime) / Double(betweenDays), 1.0) | ||
| } |
| @State var selectedTreatment: TreatmentEntity? = nil | ||
| var treatments: [TreatmentEntity] | ||
|
|
||
| let setday: (year: Int, month: Int, day: Int) | ||
| let today: (year: Int, month: Int, day: Int) |
| .frame(height: 38.adjustedH) | ||
|
|
||
| bottomView() | ||
| Spacer().frame(height: 38.adjustedH) |
| .padding(.leading, 34.adjustedW) | ||
| .padding(.trailing, 33.adjustedW) |
| TypographyText("언제까지 회복이 완료되면 좋을까요?", style: .title1_sb_18, color: .gray1000) | ||
| case .no: | ||
| TypographyText("대략적인 회복 목표일을 정해볼까요?", style: .title1_sb_18, color: .gray1000) | ||
| TypographyText("대략적인 회복 목표일을 정해볼까요?", style: .title1_sb_18, color: .gray1000) |
| extension Int { | ||
| static func daysBetween( | ||
| fromYear: Int, fromMonth: Int, fromDay: Int, | ||
| toYear: Int, toMonth: Int, toDay: Int | ||
| ) -> Int { | ||
| let calendar = Calendar.current | ||
|
|
||
| guard let fromDate = calendar.date(from: DateComponents(year: fromYear, month: fromMonth, day: fromDay)), | ||
| let toDate = calendar.date(from: DateComponents(year: toYear, month: toMonth, day: toDay)) else { | ||
| return 0 | ||
| } | ||
|
|
||
| return abs(calendar.dateComponents([.day], from: fromDate, to: toDate).day ?? 0) | ||
| } | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift`:
- Around line 52-59: Clamp the initial selectedDowntime to the picker's allowed
range instead of assigning treatment.downtimeMax directly: in the .onAppear
block (the closure that currently sets selectedDowntime = treatment.downtimeMax,
betweenDays, rate) replace that assignment with a clamped value (e.g.,
min(max(treatment.downtimeMax, 1), 30) or use the actual CherrishPicker range)
so the initial selectedDowntime always falls inside CherrishPicker(range:
1...30); keep the rest of the onAppear logic (betweenDays, rate) unchanged.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift`:
- Around line 32-44: The TreatmentRowView is passed a read-only Binding via
.constant(...) for isSelected which causes a runtime warning when
TreatmentCheckBoxView calls isSelected.toggle(); replace the .constant(...) with
a two-way Binding(get:set:) that returns whether selectedTreatment == treatment
and in the setter sets selectedTreatment = newValue ? treatment : nil (or clears
it appropriately) so toggling updates selectedTreatment; update the call site
where TreatmentRowView(...) is constructed to use this Binding and ensure
TreatmentCheckBoxView's isSelected.toggle() operates on that writable Binding.
♻️ Duplicate comments (2)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/TargetDdaySettingView.swift (1)
50-51: 반응형 높이 설정 누락다른 Spacer들은 모두
.adjustedH를 사용하고 있는데, 이 부분만40으로 하드코딩되어 있습니다. 화면 크기에 따른 일관된 레이아웃을 위해 수정이 필요합니다.🔧 수정 제안
Spacer() - .frame(height: 40) + .frame(height: 40.adjustedH)Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift (1)
175-206: 버튼 폭 계산이 외부 패딩을 고려하지 않아 오버플로우 위험
GeometryReader기준 폭으로 버튼 너비를 계산한 뒤 외부 패딩을 적용하면 전체 폭이 초과될 수 있습니다. 패딩을 내부로 옮기고 계산 폭에서 양쪽 패딩을 제외해 주세요.🛠️ 제안 수정
private var buttonView: some View { GeometryReader { geo in + let total = geo.size.width - 48.adjustedW HStack(spacing: 4.adjustedW) { CherrishButton( title: "다운타임 없이 일정 추가", type: .addEvent, state: .constant( .normal ), leadingIcon: .none, trailingIcon: .none, action: { }) - .frame(width: geo.size.width * 2/3 - 2) + .frame(width: total * 2/3 - 2) CherrishButton( title: "확인", type: .small, state: .constant( .normal ), leadingIcon: .none, trailingIcon: .none, action: { }) .frame( - width: geo.size.width * 1/3 - 2 + width: total * 1/3 - 2 ) } + .padding(.horizontal, 24.adjustedW) } .frame(height: 50.adjustedH) - .padding(.horizontal, 24.adjustedW) }
| .onAppear { | ||
| selectedDowntime = treatment.downtimeMax | ||
| betweenDays = Date.daysBetween(from: setday, to: today) ?? 0 | ||
| rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 | ||
| } | ||
| .onChange(of: selectedDowntime) { | ||
| rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate and examine the CherrishPicker component
fd -t f CherrishPickerRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 196
🏁 Script executed:
# Also find the Treatment model to check downtimeMax constraints
fd -t f TreatmentRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2047
🏁 Script executed:
# Let's examine the full DowntimeBottomSheetView file
fd -t f DowntimeBottomSheetViewRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 176
🏁 Script executed:
# Examine the DowntimeBottomSheetView file
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 8107
🏁 Script executed:
# Search for CherrishPicker definition
rg "struct CherrishPicker|class CherrishPicker" -t swift -A 20Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 2366
🏁 Script executed:
# Examine Treatment model
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/Model/Treatment.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 1674
🏁 Script executed:
# Search for TreatmentEntity definition
cat -n "Cherrish-iOS/Cherrish-iOS/Domain/Model/TreatmentEntity.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 3569
🏁 Script executed:
# Also search for CherrishPicker full implementation
cat -n "Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishPicker.swift"Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 3984
초기 선택값이 피커 범위를 벗어날 수 있습니다.
selectedDowntime를 treatment.downtimeMax로 직접 설정하면, downtimeMax가 CherrishPicker(range: 1...30) 범위를 초과하는 경우 선택기가 정상적으로 작동하지 않을 수 있습니다. 초기값을 범위 내로 클램핑해 주세요.
🛠️ 제안 수정
- selectedDowntime = treatment.downtimeMax
+ selectedDowntime = min(max(treatment.downtimeMax, 1), 30)📝 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.
| .onAppear { | |
| selectedDowntime = treatment.downtimeMax | |
| betweenDays = Date.daysBetween(from: setday, to: today) ?? 0 | |
| rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 | |
| } | |
| .onChange(of: selectedDowntime) { | |
| rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 | |
| } | |
| .onAppear { | |
| selectedDowntime = min(max(treatment.downtimeMax, 1), 30) | |
| betweenDays = Date.daysBetween(from: setday, to: today) ?? 0 | |
| rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 | |
| } | |
| .onChange(of: selectedDowntime) { | |
| rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 | |
| } |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift`
around lines 52 - 59, Clamp the initial selectedDowntime to the picker's allowed
range instead of assigning treatment.downtimeMax directly: in the .onAppear
block (the closure that currently sets selectedDowntime = treatment.downtimeMax,
betweenDays, rate) replace that assignment with a clamped value (e.g.,
min(max(treatment.downtimeMax, 1), 30) or use the actual CherrishPicker range)
so the initial selectedDowntime always falls inside CherrishPicker(range:
1...30); keep the rest of the onAppear logic (betweenDays, rate) unchanged.
| TreatmentRowView( | ||
| displayMode: .completeBoxView, | ||
| treatmentEntity: treatment, | ||
| isSelected: .constant( | ||
| selectedTreatment == treatment | ||
| ), | ||
| isCompleted: .constant( | ||
| false | ||
| ), | ||
| action: { | ||
| selectedTreatment = treatment | ||
|
|
||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "DownTimeSettingView.swift" | head -5Repository: TEAM-Cherrish/Cherrish-iOS
Length of output: 174
🏁 Script executed:
cat -n ./Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swiftRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 3800
🏁 Script executed:
find . -type f -name "*.swift" | xargs rg "struct TreatmentRowView" -lRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 161
🏁 Script executed:
cat -n ./Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/Treatment/TreatmentRowView.swiftRepository: TEAM-Cherrish/Cherrish-iOS
Length of output: 6911
읽기 전용 Binding으로 인해 토글 시 런타임 경고 유발
TreatmentCheckBoxView에서 isSelected.toggle()을 호출하는데(167줄), .constant() 바인딩은 읽기 전용이므로 런타임 경고를 유발합니다. 선택/해제 토글이 제대로 작동하도록 Binding(get:set:)으로 수정하세요.
🛠️ 제안 수정
- isSelected: .constant(
- selectedTreatment == treatment
- ),
+ isSelected: Binding(
+ get: { selectedTreatment == treatment },
+ set: { newValue in
+ selectedTreatment = newValue ? treatment : nil
+ }
+ ),🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift`
around lines 32 - 44, The TreatmentRowView is passed a read-only Binding via
.constant(...) for isSelected which causes a runtime warning when
TreatmentCheckBoxView calls isSelected.toggle(); replace the .constant(...) with
a two-way Binding(get:set:) that returns whether selectedTreatment == treatment
and in the setter sets selectedTreatment = newValue ? treatment : nil (or clears
it appropriately) so toggling updates selectedTreatment; update the call site
where TreatmentRowView(...) is constructed to use this Binding and ensure
TreatmentCheckBoxView's isSelected.toggle() operates on that writable Binding.

🔗 연결된 이슈
📄 작업 내용
💻 주요 코드 설명
DowntimeBottomSheetView-화면크기에 너비 자동 조절하게 구현
Int+View+