Conversation
📝 WalkthroughWalkthrough홈 화면의 예정된 항목 선택을 통해 캘린더 뷰로 네비게이션하고 선택된 날짜를 전달하는 흐름을 구현. HomeCalendarFlowState를 중앙 상태 관리 객체로 도입하여 홈 뷰와 캘린더 뷰 간의 데이터 흐름을 연결. 캘린더 셀 텍스트 색상 로직을 리팩토링하고, 의존성 주입 설정을 업데이트함. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant HomeView
participant HomeViewModel
participant HomeCalendarFlowState
participant CalendarView
participant CalendarViewModel
User->>HomeView: UpcomingBoxView 항목 탭
HomeView->>HomeViewModel: selectUpcomingDate(dateString)
HomeViewModel->>HomeCalendarFlowState: treatmentDate = parsedDate
HomeView->>HomeView: tabBarCoordinator.switchTab(.calendar)
CalendarView->>HomeCalendarFlowState: homeCalendarFlowState 구독
HomeCalendarFlowState-->>CalendarView: treatmentDate 변경 감지
CalendarView->>CalendarViewModel: updateDate(date)
CalendarViewModel->>CalendarViewModel: selectedDate 업데이트<br/>currentMonth 계산
CalendarViewModel->>CalendarViewModel: 비동기 데이터 갱신<br/>(procedureCounts, todayProcedureList)
CalendarView->>HomeCalendarFlowState: treatmentDate = nil (초기화)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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: 5
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`:
- Line 27: The property wrapper on homeCalendarFlowState is incorrect for an
externally injected object; replace the `@StateObject` declaration of
homeCalendarFlowState with `@ObservedObject` in CalendarView so the view does not
claim lifecycle ownership (homeCalendarFlowState is provided via
DIContainer/singleton). Update the CalendarView property declaration that
currently reads "@StateObject var homeCalendarFlowState: HomeCalendarFlowState"
to use "@ObservedObject" so the semantics match external injection.
- Around line 69-80: Extract the duplicated logic that checks
homeCalendarFlowState.treatmentDate, calls viewModel.updateDate(date:), and sets
homeCalendarFlowState.treatmentDate = nil into a single helper method (e.g.,
handleTreatmentDate()) inside CalendarView; then replace the bodies of both the
.onChange(of: homeCalendarFlowState.treatmentDate) closure and the .onAppear
closure to call that helper, ensuring you reference the same
viewModel.updateDate(date:) and mutate homeCalendarFlowState.treatmentDate from
within the helper so behavior remains identical.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`:
- Around line 141-148: The code launches detached Tasks in CalendarViewModel
without cancellation, so rapid updateDate calls or view disappearance can spawn
overlapping tasks and stale results; add a stored Task? property (e.g.
fetchTask) on CalendarViewModel, cancel the existing fetchTask before creating a
new Task in updateDate, assign the new Task to fetchTask, and ensure you cancel
fetchTask in deinit or when the view disappears; also handle CancellationError
inside the Task (or check Task.isCancelled) so fetchProcedureCountsOfMonth and
fetchTodayProcedureList results are not applied after cancellation.
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift`:
- Around line 81-85: 포맷팅 변경이 기능과 직접 관련되지 않으므로, TreatmentViewModel.swift의
createUserProcedureUseCase.execute(...) 호출부에서 여러 줄로 나눈 포맷팅 변경을 별도의 커밋(또는 PR)으로
분리하세요: 현재 변경된 createUserProcedureUseCase.execute 호출을 원래 기능 관련 커밋에서 제거하고 포맷팅 전/후
중 원하는 스타일을 반영한 작은 커밋을 새로 만들어 푸시한 뒤 이 PR은 캘린더 네비게이션 관련 변경만 포함하도록 정리하십시오.
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`:
- Around line 41-46: The date parsing in selectUpcomingDate(_:) uses a fixed
format but doesn't set the DateFormatter.locale, calendar, or timeZone, which
can cause failures on some devices; update the DateFormatter in
selectUpcomingDate to set formatter.locale = Locale(identifier: "en_US_POSIX"),
formatter.calendar = Calendar(identifier: .gregorian) and (if backend expects a
specific zone) set formatter.timeZone appropriately before calling
formatter.date(from:), then assign the parsed Date to
homeCalendarFlowState.treatmentDate.
| struct CalendarView: View { | ||
| @EnvironmentObject private var calendarCoordinator: CalendarCoordinator | ||
| @StateObject var viewModel: CalendarViewModel | ||
| @StateObject var homeCalendarFlowState: HomeCalendarFlowState |
There was a problem hiding this comment.
@StateObject 대신 @ObservedObject를 사용해야 합니다.
homeCalendarFlowState는 DIContainer에서 외부 생성되어 주입되는 객체입니다. @StateObject는 View가 객체의 생명주기를 소유할 때 사용하고, 외부에서 생성된 객체를 전달받을 때는 @ObservedObject를 사용해야 합니다.
현재 구현에서는 싱글톤으로 등록되어 있어 실질적으로 문제가 발생하지 않을 수 있지만, 의미론적으로 올바른 property wrapper를 사용하는 것이 좋습니다.
🔧 수정 제안
- `@StateObject` var homeCalendarFlowState: HomeCalendarFlowState
+ `@ObservedObject` var homeCalendarFlowState: HomeCalendarFlowState📝 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.
| @StateObject var homeCalendarFlowState: HomeCalendarFlowState | |
| `@ObservedObject` var homeCalendarFlowState: HomeCalendarFlowState |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
at line 27, The property wrapper on homeCalendarFlowState is incorrect for an
externally injected object; replace the `@StateObject` declaration of
homeCalendarFlowState with `@ObservedObject` in CalendarView so the view does not
claim lifecycle ownership (homeCalendarFlowState is provided via
DIContainer/singleton). Update the CalendarView property declaration that
currently reads "@StateObject var homeCalendarFlowState: HomeCalendarFlowState"
to use "@ObservedObject" so the semantics match external injection.
| .onChange(of: homeCalendarFlowState.treatmentDate) { date in | ||
| if let date = date { | ||
| viewModel.updateDate(date: date) | ||
| homeCalendarFlowState.treatmentDate = nil | ||
| } | ||
| } | ||
| .onAppear { | ||
| if let date = homeCalendarFlowState.treatmentDate { | ||
| viewModel.updateDate(date: date) | ||
| homeCalendarFlowState.treatmentDate = nil | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
중복 로직을 헬퍼 함수로 추출하는 것을 고려해보세요.
onAppear와 onChange 모두 동일한 로직(updateDate 호출 후 treatmentDate = nil 설정)을 수행합니다. 중복을 줄이기 위해 헬퍼 함수로 추출할 수 있습니다.
♻️ 제안하는 리팩토링
+ private func handleTreatmentDate(_ date: Date?) {
+ guard let date = date else { return }
+ viewModel.updateDate(date: date)
+ homeCalendarFlowState.treatmentDate = nil
+ }
+
// In body:
.onChange(of: homeCalendarFlowState.treatmentDate) { date in
- if let date = date {
- viewModel.updateDate(date: date)
- homeCalendarFlowState.treatmentDate = nil
- }
+ handleTreatmentDate(date)
}
.onAppear {
- if let date = homeCalendarFlowState.treatmentDate {
- viewModel.updateDate(date: date)
- homeCalendarFlowState.treatmentDate = nil
- }
+ handleTreatmentDate(homeCalendarFlowState.treatmentDate)
}📝 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.
| .onChange(of: homeCalendarFlowState.treatmentDate) { date in | |
| if let date = date { | |
| viewModel.updateDate(date: date) | |
| homeCalendarFlowState.treatmentDate = nil | |
| } | |
| } | |
| .onAppear { | |
| if let date = homeCalendarFlowState.treatmentDate { | |
| viewModel.updateDate(date: date) | |
| homeCalendarFlowState.treatmentDate = nil | |
| } | |
| } | |
| private func handleTreatmentDate(_ date: Date?) { | |
| guard let date = date else { return } | |
| viewModel.updateDate(date: date) | |
| homeCalendarFlowState.treatmentDate = nil | |
| } | |
| // ... (other code in body) | |
| .onChange(of: homeCalendarFlowState.treatmentDate) { date in | |
| handleTreatmentDate(date) | |
| } | |
| .onAppear { | |
| handleTreatmentDate(homeCalendarFlowState.treatmentDate) | |
| } |
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
around lines 69 - 80, Extract the duplicated logic that checks
homeCalendarFlowState.treatmentDate, calls viewModel.updateDate(date:), and sets
homeCalendarFlowState.treatmentDate = nil into a single helper method (e.g.,
handleTreatmentDate()) inside CalendarView; then replace the bodies of both the
.onChange(of: homeCalendarFlowState.treatmentDate) closure and the .onAppear
closure to call that helper, ensuring you reference the same
viewModel.updateDate(date:) and mutate homeCalendarFlowState.treatmentDate from
within the helper so behavior remains identical.
| Task { | ||
| do { | ||
| try await fetchProcedureCountsOfMonth() | ||
| try await fetchTodayProcedureList() | ||
| } catch { | ||
| CherrishLogger.error(error) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
비동기 작업의 취소 처리가 누락되었습니다.
updateDate가 빠르게 연속 호출되면 여러 Task가 동시에 실행되어 이전 요청의 결과가 최신 상태를 덮어쓸 수 있습니다. 또한, view가 사라질 때 Task가 취소되지 않습니다.
♻️ Task 관리 개선 제안
ViewModel에 Task 참조를 저장하고 새 호출 시 이전 Task를 취소하는 방식을 고려해보세요:
+ private var updateTask: Task<Void, Never>?
+
func updateDate(date: Date) {
selectedDate = date
let calendar = Calendar.current
let currentYear = calendar.component(.year, from: currentDate)
let currentMonthVal = calendar.component(.month, from: currentDate)
let targetYear = calendar.component(.year, from: date)
let targetMonthVal = calendar.component(.month, from: date)
let monthDiff = (targetYear - currentYear) * 12 + (targetMonthVal - currentMonthVal)
currentMonth = monthDiff
- Task {
+ updateTask?.cancel()
+ updateTask = Task {
do {
try await fetchProcedureCountsOfMonth()
try await fetchTodayProcedureList()
} catch {
CherrishLogger.error(error)
}
}
}🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 141 - 148, The code launches detached Tasks in CalendarViewModel
without cancellation, so rapid updateDate calls or view disappearance can spawn
overlapping tasks and stale results; add a stored Task? property (e.g.
fetchTask) on CalendarViewModel, cancel the existing fetchTask before creating a
new Task in updateDate, assign the new Task to fetchTask, and ensure you cancel
fetchTask in deinit or when the view disappears; also handle CancellationError
inside the Task (or check Task.isCancelled) so fetchProcedureCountsOfMonth and
fetchTodayProcedureList results are not applied after cancellation.
| try await createUserProcedureUseCase.execute( | ||
| scheduledDate: scheduledDate.toScheduledAtFormat, | ||
| recoveryDate: recoverDate.toRecoveryDateFormat, | ||
| treatments: selectedTreatments | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
코드 포맷팅 개선 확인
함수 호출을 여러 줄로 분리하여 가독성을 향상시켰습니다. 파라미터 순서와 값은 변경되지 않았으며, 로직에 영향을 주지 않는 포맷팅 변경입니다.
다만, 이 변경사항은 PR의 주요 목적(캘린더 선택 날짜 네비게이션)과 직접적인 관련이 없는 것으로 보입니다. 포맷팅 개선은 좋지만, 별도의 커밋이나 PR로 분리하는 것이 변경 이력 관리에 더 명확할 수 있습니다.
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift`
around lines 81 - 85, 포맷팅 변경이 기능과 직접 관련되지 않으므로, TreatmentViewModel.swift의
createUserProcedureUseCase.execute(...) 호출부에서 여러 줄로 나눈 포맷팅 변경을 별도의 커밋(또는 PR)으로
분리하세요: 현재 변경된 createUserProcedureUseCase.execute 호출을 원래 기능 관련 커밋에서 제거하고 포맷팅 전/후
중 원하는 스타일을 반영한 작은 커밋을 새로 만들어 푸시한 뒤 이 PR은 캘린더 네비게이션 관련 변경만 포함하도록 정리하십시오.
| func selectUpcomingDate(_ dateString: String) { | ||
| let formatter = DateFormatter() | ||
| formatter.dateFormat = "yyyy-MM-dd" | ||
| if let date = formatter.date(from: dateString) { | ||
| homeCalendarFlowState.treatmentDate = date | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Apple DateFormatter fixed format parsing locale calendar en_US_POSIX best practices
💡 Result:
Short best-practice checklist for fixed-format parsing with DateFormatter (Swift / Foundation)
- Use a POSIX locale: formatter.locale = Locale(identifier: "en_US_POSIX") — this prevents user settings (24‑hour, language, etc.) from changing your format. (developer.apple.com)
- Set an explicit calendar (usually Gregorian) when you need calendar-stable parsing: formatter.calendar = Calendar(identifier: .gregorian). Some locales use non‑Gregorian calendars which can change numeric interpretation. (stackoverflow.com)
- Set an explicit timeZone for parsing (often UTC for internet timestamps): formatter.timeZone = TimeZone(secondsFromGMT: 0). (developer.apple.com)
- Prefer specialized APIs when available: use ISO8601DateFormatter for ISO 8601 / RFC 3339 strings (less error-prone and faster). (sarunw.com)
- For user-visible output, avoid fixed dateFormat; use dateStyle/timeStyle or localized templates (DateFormatter.dateFormatFromTemplate or setLocalizedDateFormatFromTemplate) so the UI respects user preferences. (developer.apple.com)
- Cache formatters but handle locale changes: either create POSIX parsers on demand (they’re locale-invariant) or observe NSCurrentLocaleDidChangeNotification and rebuild cached formatters when user locale changes. (developer.apple.com)
- When strict fixed-format parsing is critical, consider C functions (strptime_l/strftime_l) with the POSIX/C locale for deterministic behavior. (developer.apple.com)
Minimal example (Swift):
let f = DateFormatter()
f.locale = Locale(identifier: "en_US_POSIX")
f.calendar = Calendar(identifier: .gregorian)
f.timeZone = TimeZone(secondsFromGMT: 0)
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date = f.date(from: isoString) // deterministic parsing
(Or use ISO8601DateFormatter for RFC 3339/ISO 8601 inputs.) (sarunw.com)
References (Apple):
- Date formatters / fixed formats guidance. (developer.apple.com)
- Technical Q&A QA1480 (NSDateFormatter and Internet dates). (developer.apple.com)
Citations:
- 1: https://developer.apple.com/library/archive/qa/qa1480/_index.html?utm_source=openai
- 2: https://stackoverflow.com/questions/72406774/dateformatter-returns-nil-with-specific-combination-of-timezone-and-locale?utm_source=openai
- 3: https://developer.apple.com/library/archive/qa/qa1480/_index.html?utm_source=openai
- 4: https://sarunw.com/posts/how-to-parse-iso8601-date-in-swift/?utm_source=openai
- 5: https://developer.apple.com/library/archive/qa/qa1480/_index.html?utm_source=openai
- 6: https://developer.apple.com/library/archive/qa/qa1480/_index.html?utm_source=openai
- 7: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html?utm_source=openai
- 8: https://sarunw.com/posts/how-to-parse-iso8601-date-in-swift/?utm_source=openai
- 9: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html?utm_source=openai
- 10: https://developer.apple.com/library/archive/qa/qa1480/_index.html?utm_source=openai
고정 포맷 파싱 시 Locale/Calendar 명시 필요.
현재 코드에서 명시적인 로케일과 캘린더가 없으므로 "yyyy-MM-dd" 파싱이 사용자의 기기 설정에 따라 실패할 수 있습니다. Apple의 공식 가이드에 따르면 고정 포맷 파싱에는 en_US_POSIX 로케일과 그레고리력을 명시해야 하며, 백엔드 기준 타임존이 있다면 timeZone도 지정해주세요.
🔧 제안 수정
- let formatter = DateFormatter()
- formatter.dateFormat = "yyyy-MM-dd"
+ let formatter = DateFormatter()
+ formatter.locale = Locale(identifier: "en_US_POSIX")
+ formatter.calendar = Calendar(identifier: .gregorian)
+ formatter.dateFormat = "yyyy-MM-dd"🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeViewModel.swift`
around lines 41 - 46, The date parsing in selectUpcomingDate(_:) uses a fixed
format but doesn't set the DateFormatter.locale, calendar, or timeZone, which
can cause failures on some devices; update the DateFormatter in
selectUpcomingDate to set formatter.locale = Locale(identifier: "en_US_POSIX"),
formatter.calendar = Calendar(identifier: .gregorian) and (if backend expects a
specific zone) set formatter.timeZone appropriately before calling
formatter.date(from:), then assign the parsed Date to
homeCalendarFlowState.treatmentDate.
🔗 연결된 이슈
📄 작업 내용