Skip to content

Refactor/#172 로딩뷰 추가#173

Merged
y-eonee merged 4 commits intodevelopfrom
fix/#172-로딩뷰
Jan 23, 2026

Hidden character warning

The head ref may contain hidden characters: "fix/#172-\ub85c\ub529\ubdf0"
Merged

Refactor/#172 로딩뷰 추가#173
y-eonee merged 4 commits intodevelopfrom
fix/#172-로딩뷰

Conversation

@y-eonee
Copy link
Contributor

@y-eonee y-eonee commented Jan 23, 2026

🔗 연결된 이슈

📄 작업 내용

  • 로딩뷰 걸었어요
  • CherrishLoadingView를 만들었음 아주 간단함
  • viewModel에다가 isLoading Published로 걸고 분기처리해서 띄워주면 돼염

@y-eonee y-eonee requested a review from a team January 23, 2026 08:22
@y-eonee y-eonee self-assigned this Jan 23, 2026
@y-eonee y-eonee requested review from soseoyo12, sum130 and wotjs020708 and removed request for a team January 23, 2026 08:22
@y-eonee y-eonee linked an issue Jan 23, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

여러 화면에 공통 로딩 상태(isLoading)가 도입되고, CalendarView가 외/내부 뷰로 분리되어 상태 바인딩(calendarMode, selectedProcedureID)이 상위로 이동했습니다. 전역 로딩 컴포넌트 CherrishLoadingView가 추가되었고 여러 뷰에서 로딩 분기 및 .task 위치가 조정되었습니다.

Changes

Cohort / File(s) 요약
공통 로딩 컴포넌트
Cherrish-iOS/.../Presentation/Global/Components/CherrishLoadingView.swift
신규 CherrishLoadingView 추가(원형 ProgressView).
Calendar: 뷰 구조 분리 및 상태 이동
Cherrish-iOS/.../Presentation/Feature/Calendar/CalendarMain/CalendarView.swift
CalendarContentView 분리, 상위에 @State var calendarMode@State var selectedProcedureID 추가; 하위로 @Binding 전달. 외부의 task/onAppear/onChange 기반 데이터 페칭/데이트 업데이트 로직 제거. 여러 extension(헤더·데이트 그리드·스케줄 등)을 하위로 이동.
Calendar: 뷰모델 로딩 상태 추가
Cherrish-iOS/.../Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift
isLoading 추가 및 주요 fetch 메서드에 isLoading 토글과 do-catch + 로깅 적용(오류 전파 제거).
Treatment / NoTreatment: 뷰 로딩 분기 및 레이아웃 조정
Cherrish-iOS/.../Presentation/Feature/Calendar/Treatment/View/...
다수 Treatment/NoTreatment 뷰에 isLoading 분기 추가(로딩 시 CherrishLoadingView), ScrollView/Spacer/padding 조정, 일부 .task 위치 조정 및 선택 로직 수선.
Treatment: 뷰모델 로딩 상태 추가
Cherrish-iOS/.../Presentation/Feature/Calendar/Treatment/ViewModel/...
NoTreatmentViewModel, TreatmentViewModel 등에 @Published private(set) var isLoading 추가 및 fetch 래핑. 일부 에러 경로에서 isLoading 복원 누락 확인 필요.
Home / MyPage / Challenge: 콘텐츠 분리 및 루트 로딩 처리
Cherrish-iOS/.../Presentation/Feature/Home/HomeView.swift
Cherrish-iOS/.../Presentation/Feature/MyPage/MyPageView.swift
Cherrish-iOS/.../Presentation/Feature/ChallengeView/View/ChallengeProgressView.swift
루트에 ZStack 기반 로딩 분기 추가(로딩 시 CherrishLoadingView), 콘텐츠를 *ContentView로 분리, 데이터 로드 트리거를 상위 .task로 이동.
사소한 포맷 수정
Cherrish-iOS/.../Presentation/Feature/ChallengeView/View/ChallengeSelectMissionView.swift
공백(포맷) 정리(비기능적).

Sequence Diagram(s)

(생략)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25분

Possibly related PRs

Suggested reviewers

  • wotjs020708
  • sum130
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 로딩뷰 추가라는 주요 변경사항을 명확하게 요약하고 있으며, 변경사항의 핵심 내용을 잘 반영합니다.
Description check ✅ Passed PR 설명은 로딩뷰 추가, CherrishLoadingView 생성, viewModel의 isLoading 프로퍼티 추가 등 변경사항과 관련된 내용을 포함하고 있습니다.
Linked Issues check ✅ Passed PR은 로딩뷰 기능을 추가하여 이슈 #172의 목표를 충족합니다: CherrishLoadingView 컴포넌트 생성 및 isLoading 프로퍼티를 통한 로딩 상태 관리 구현.
Out of Scope Changes check ✅ Passed 변경사항은 로딩뷰 관련 기능에 집중되어 있으며, 이슈 #172의 범위 내에 있습니다. 로딩뷰 추가와 관련된 변경만 포함됩니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/#172-로딩뷰

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@y-eonee y-eonee added 나연🐹 Refactor 코드 리팩토링 시 사용 labels Jan 23, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

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/MyPage/MyPageView.swift (1)

29-35: 중복 에러 처리: ViewModel과 View 양쪽에서 에러를 처리하고 있습니다.

fetchUserInfo()는 내부에서 이미 에러를 catch하고 로깅합니다. 하지만 함수 시그니처가 throws이고 View에서도 try-catch로 감싸고 있어 혼란스럽습니다. ViewModel에서 에러를 삼키므로 실제로 View의 catch 블록은 실행되지 않습니다.

일관성을 위해 throws를 제거하거나, ViewModel에서 에러를 다시 던지도록 수정하는 것을 권장합니다.

🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`:
- Around line 26-29: The SwiftUI state properties are missing access control;
mark the `@StateObject` and `@State` properties as private to satisfy SwiftLint and
encapsulate internal state—update the declarations for viewModel,
homeCalendarFlowState, calendarMode, and selectedProcedureID in CalendarView
(e.g., change "@StateObject var viewModel" to "private `@StateObject` var
viewModel", and similarly make homeCalendarFlowState, calendarMode, and
selectedProcedureID private).
- Around line 44-52: The two sequential calls
viewModel.fetchProcedureCountsOfMonth and viewModel.fetchTodayProcedureList
cause isLoading toggles and potential flicker and also prevent the second call
if the first fails; fix by either (A) adding a ViewModel wrapper like
fetchMonthData() that sets isLoading = true once, starts both operations (e.g.,
using async let or TaskGroup inside the wrapper), awaits both, aggregates
errors, then sets isLoading = false, and call that from the .task, or (B) change
the .task body to run both in parallel (use async let for
viewModel.fetchProcedureCountsOfMonth and viewModel.fetchTodayProcedureList) and
handle errors so both run and you control a single loading flag (ensure you
reference viewModel.isLoading or the new wrapper method names to locate the
changes).
- Around line 71-72: The properties viewModel and homeCalendarFlowState in
CalendarView (declared as `@StateObject` var viewModel and `@StateObject` var
homeCalendarFlowState) are being passed in from a parent and should be observed,
not owned; change both declarations to use `@ObservedObject` instead of
`@StateObject` (so CalendarView/CalendarContentView observes externally-owned
CalendarViewModel and HomeCalendarFlowState) and ensure you do not reinitialize
them in this view's init.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`:
- Around line 127-132: The catch block currently logs errors but fails to reset
isLoading, causing the UI to remain in a loading state; update the error
handling for fetchTodayProcedureListUseCase.execute (used to assign
procedureList) so that isLoading is set to false in all paths — either by
setting isLoading = false inside the catch block alongside
CherrishLogger.error(error) or by restructuring the async call with a
defer/finally-like pattern to guarantee isLoading is cleared after the await
completes or errors.
- Around line 113-119: The do-catch in CalendarViewModel around
fetchProcedureCountOfMonthUseCase.execute currently sets isLoading = false only
on success, causing infinite loading on errors; update the function so isLoading
is always set to false after the await (either add isLoading = false in the
catch block or use a defer/finally-style pattern) and keep assigning
procedureCountOfMonth from response only on success—refer to CalendarViewModel
and the fetchProcedureCountOfMonthUseCase.execute call to locate the code to
change.
- Around line 139-146: The catch path for the
fetchProcedureDowntimeUseCase.execute call doesn't reset isLoading, causing the
view to remain stuck loading; update the error handling so isLoading is set to
false on failure (either add isLoading = false inside the catch before
CherrishLogger.error(error) or refactor the surrounding logic to use a defer
that sets isLoading = false after starting the async work), keeping the rest of
the flow (selectedDowntime, mapToDowntimeDays, CherrishLogger.error) unchanged.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeProgressView.swift`:
- Around line 70-73: The ChallengeProgressContentView is incorrectly declaring
its injected viewModel as `@StateObject`; change the property declaration in
ChallengeProgressContentView from "@StateObject var viewModel:
ChallengeProgressViewModel" to use "@ObservedObject" so the view observes the
externally created ChallengeProgressViewModel passed in from
ChallengeProgressView; ensure the parent (ChallengeProgressView) remains the
owner (uses `@StateObject`) and only pass the viewModel instance into
ChallengeProgressContentView.

In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift`:
- Around line 27-29: HomeContentView currently declares its injected view model
as `@StateObject` which incorrectly claims ownership; change the property wrapper
on viewModel from `@StateObject` to `@ObservedObject` so HomeContentView observes
the parent-provided HomeViewModel instead (update the declaration of viewModel
in the HomeContentView struct to use `@ObservedObject` and keep the type
HomeViewModel).

In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/MyPage/MyPageViewModel.swift`:
- Around line 25-32: The catch block in MyPageViewModel leaves isLoading true on
errors causing infinite loading; update the error path so isLoading is set to
false after fetchUserInfoUseCase.execute() fails — either add isLoading = false
inside the catch that currently calls CherrishLogger.error(error), or refactor
the async call in the function (the method containing
fetchUserInfoUseCase.execute()) to use a defer that sets isLoading = false to
guarantee it is cleared on both success and failure.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishLoadingView.swift`:
- Around line 10-15: 현재 CherrishLoadingView의 ProgressView가 부모 레이아웃에 따라 위치가 달라질 수
있으니 CherrishLoadingView의 body(ProgressView)를 화면 전체를 차지하도록 확장하고 중앙에 정렬되게 변경하세요;
구체적으로 struct CherrishLoadingView의 body에서 ProgressView를 감싸는 컨테이너(예: ZStack 또는
VStack)를 사용하거나 ProgressView에 frame(maxWidth: .infinity, maxHeight: .infinity) 및
.frame 정렬/alignment를 적용하여 수평·수직 중앙 정렬을 보장하도록 수정하세요.

Comment on lines 26 to 29
@StateObject var viewModel: CalendarViewModel
@StateObject var homeCalendarFlowState: HomeCalendarFlowState
@State var calendarMode: CalendarMode = .none
@State var selectedProcedureID: Int? = nil
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

SwiftUI state 속성에 private 접근 제어자를 추가하세요.

SwiftLint 경고에 따르면, SwiftUI의 @StateObject@State 속성은 private으로 선언해야 합니다. 특히 calendarModeselectedProcedureID는 내부 상태이므로 반드시 private이어야 합니다.

♻️ 권장 수정안
 struct CalendarView: View {
     `@EnvironmentObject` private var calendarCoordinator: CalendarCoordinator
-    `@StateObject` var viewModel: CalendarViewModel
-    `@StateObject` var homeCalendarFlowState: HomeCalendarFlowState
-    `@State` var calendarMode: CalendarMode = .none
-    `@State` var selectedProcedureID: Int? = nil
+    `@StateObject` private var viewModel: CalendarViewModel
+    `@StateObject` private var homeCalendarFlowState: HomeCalendarFlowState
+    `@State` private var calendarMode: CalendarMode = .none
+    `@State` private var selectedProcedureID: Int? = nil
📝 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.

Suggested change
@StateObject var viewModel: CalendarViewModel
@StateObject var homeCalendarFlowState: HomeCalendarFlowState
@State var calendarMode: CalendarMode = .none
@State var selectedProcedureID: Int? = nil
`@StateObject` private var viewModel: CalendarViewModel
`@StateObject` private var homeCalendarFlowState: HomeCalendarFlowState
`@State` private var calendarMode: CalendarMode = .none
`@State` private var selectedProcedureID: Int? = nil
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 26-26: SwiftUI state properties should be private

(private_swiftui_state)


[Warning] 27-27: SwiftUI state properties should be private

(private_swiftui_state)


[Warning] 28-28: SwiftUI state properties should be private

(private_swiftui_state)


[Warning] 29-29: SwiftUI state properties should be private

(private_swiftui_state)

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
around lines 26 - 29, The SwiftUI state properties are missing access control;
mark the `@StateObject` and `@State` properties as private to satisfy SwiftLint and
encapsulate internal state—update the declarations for viewModel,
homeCalendarFlowState, calendarMode, and selectedProcedureID in CalendarView
(e.g., change "@StateObject var viewModel" to "private `@StateObject` var
viewModel", and similarly make homeCalendarFlowState, calendarMode, and
selectedProcedureID private).

Comment on lines +44 to +52
.task (id: viewModel.currentMonth){
if calendarMode == .none {
do {
try await viewModel.fetchProcedureCountsOfMonth()
try await viewModel.fetchTodayProcedureList()
} catch {
CherrishLogger.error(error)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

순차적 fetch 호출로 인한 로딩 상태 깜빡임(flicker) 가능성이 있습니다.

fetchProcedureCountsOfMonthfetchTodayProcedureList 모두 내부에서 isLoading = true로 설정 후 완료 시 false로 설정합니다. 순차 호출 시 true → false → true → false 순서로 변경되어 로딩 뷰가 깜빡일 수 있습니다.

또한, 첫 번째 호출이 실패하면 두 번째 호출이 실행되지 않아 불완전한 상태가 될 수 있습니다.

🔧 권장 수정안

ViewModel에서 로딩 상태를 별도로 관리하거나, 두 호출을 병렬로 실행하고 외부에서 로딩 상태를 제어하는 방식을 고려하세요:

 .task (id: viewModel.currentMonth){
     if calendarMode == .none {
+        viewModel.isLoading = true
         do {
-            try await viewModel.fetchProcedureCountsOfMonth()
-            try await viewModel.fetchTodayProcedureList()
+            async let counts: () = viewModel.fetchProcedureCountsOfMonthWithoutLoading()
+            async let list: () = viewModel.fetchTodayProcedureListWithoutLoading()
+            _ = try await (counts, list)
         } catch {
             CherrishLogger.error(error)
         }
+        viewModel.isLoading = false
     }
 }

또는 ViewModel에 두 작업을 래핑하는 단일 메서드를 추가하세요.

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift`
around lines 44 - 52, The two sequential calls
viewModel.fetchProcedureCountsOfMonth and viewModel.fetchTodayProcedureList
cause isLoading toggles and potential flicker and also prevent the second call
if the first fails; fix by either (A) adding a ViewModel wrapper like
fetchMonthData() that sets isLoading = true once, starts both operations (e.g.,
using async let or TaskGroup inside the wrapper), awaits both, aggregates
errors, then sets isLoading = false, and call that from the .task, or (B) change
the .task body to run both in parallel (use async let for
viewModel.fetchProcedureCountsOfMonth and viewModel.fetchTodayProcedureList) and
handle errors so both run and you control a single loading flag (ensure you
reference viewModel.isLoading or the new wrapper method names to locate the
changes).

Comment on lines +113 to +119
do {
let response = try await fetchProcedureCountOfMonthUseCase.execute(year: year, month: month)
isLoading = false
procedureCountOfMonth = response.dailyProcedureCounts
} catch {
CherrishLogger.error(error)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

에러 발생 시 isLoadingfalse로 설정되지 않아 무한 로딩 상태가 됩니다.

catch 블록에서 isLoading = false가 누락되어 있습니다. 네트워크 오류 발생 시 사용자는 영원히 로딩 화면을 보게 됩니다. HomeViewModel.loadDashboard()의 패턴처럼 isLoading = falsedo-catch 이후 또는 catch 블록 내에서 호출해야 합니다.

🐛 수정 제안
         do {
             let response = try await fetchProcedureCountOfMonthUseCase.execute(year: year, month: month)
-            isLoading = false
             procedureCountOfMonth = response.dailyProcedureCounts
         } catch {
             CherrishLogger.error(error)
         }
+        isLoading = false
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 113 - 119, The do-catch in CalendarViewModel around
fetchProcedureCountOfMonthUseCase.execute currently sets isLoading = false only
on success, causing infinite loading on errors; update the function so isLoading
is always set to false after the await (either add isLoading = false in the
catch block or use a defer/finally-style pattern) and keep assigning
procedureCountOfMonth from response only on success—refer to CalendarViewModel
and the fetchProcedureCountOfMonthUseCase.execute call to locate the code to
change.

Copy link
Contributor

Choose a reason for hiding this comment

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

이고 한 번 화긴해주세용!

Comment on lines +127 to +132
do {
procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate)
isLoading = false
} catch {
CherrishLogger.error(error)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

동일한 이슈: 에러 시 isLoading이 초기화되지 않습니다.

🐛 수정 제안
         do {
             procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate)
-            isLoading = false
         } catch {
             CherrishLogger.error(error)
         }
+        isLoading = false
📝 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.

Suggested change
do {
procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate)
isLoading = false
} catch {
CherrishLogger.error(error)
}
do {
procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate)
} catch {
CherrishLogger.error(error)
}
isLoading = false
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 127 - 132, The catch block currently logs errors but fails to reset
isLoading, causing the UI to remain in a loading state; update the error
handling for fetchTodayProcedureListUseCase.execute (used to assign
procedureList) so that isLoading is set to false in all paths — either by
setting isLoading = false inside the catch block alongside
CherrishLogger.error(error) or by restructuring the async call with a
defer/finally-like pattern to guarantee isLoading is cleared after the await
completes or errors.

Comment on lines +139 to +146
do {
let downtimeList = try await fetchProcedureDowntimeUseCase.execute(id: procedureId)
isLoading = false
selectedDowntime = downtimeList
mapToDowntimeDays(procedure: downtimeList)
} catch {
CherrishLogger.error(error)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

동일한 이슈: 에러 시 isLoading이 초기화되지 않습니다.

🐛 수정 제안
         do {
             let downtimeList = try await fetchProcedureDowntimeUseCase.execute(id: procedureId)
-            isLoading = false 
             selectedDowntime = downtimeList
             mapToDowntimeDays(procedure: downtimeList)
         } catch {
             CherrishLogger.error(error)
         }
+        isLoading = false
📝 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.

Suggested change
do {
let downtimeList = try await fetchProcedureDowntimeUseCase.execute(id: procedureId)
isLoading = false
selectedDowntime = downtimeList
mapToDowntimeDays(procedure: downtimeList)
} catch {
CherrishLogger.error(error)
}
do {
let downtimeList = try await fetchProcedureDowntimeUseCase.execute(id: procedureId)
selectedDowntime = downtimeList
mapToDowntimeDays(procedure: downtimeList)
} catch {
CherrishLogger.error(error)
}
isLoading = false
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift`
around lines 139 - 146, The catch path for the
fetchProcedureDowntimeUseCase.execute call doesn't reset isLoading, causing the
view to remain stuck loading; update the error handling so isLoading is set to
false on failure (either add isLoading = false inside the catch before
CherrishLogger.error(error) or refactor the surrounding logic to use a defer
that sets isLoading = false after starting the async work), keeping the rest of
the flow (selectedDowntime, mapToDowntimeDays, CherrishLogger.error) unchanged.

Comment on lines +25 to +32
do {
let response = try await fetchUserInfoUseCase.execute()
isLoading = false
name = response.name
day = response.daysSinceSignup
} catch {
CherrishLogger.error(error)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

에러 발생 시 isLoadingfalse로 설정되지 않습니다.

CalendarViewModel과 동일한 이슈입니다. catch 블록 실행 후에도 isLoadingtrue로 유지되어 에러 발생 시 무한 로딩 상태가 됩니다.

🐛 수정 제안
         do {
             let response = try await fetchUserInfoUseCase.execute()
-            isLoading = false
             name = response.name
             day = response.daysSinceSignup
         } catch {
             CherrishLogger.error(error)
         }
+        isLoading = false
📝 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.

Suggested change
do {
let response = try await fetchUserInfoUseCase.execute()
isLoading = false
name = response.name
day = response.daysSinceSignup
} catch {
CherrishLogger.error(error)
}
do {
let response = try await fetchUserInfoUseCase.execute()
name = response.name
day = response.daysSinceSignup
} catch {
CherrishLogger.error(error)
}
isLoading = false
🤖 Prompt for AI Agents
In `@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/MyPage/MyPageViewModel.swift`
around lines 25 - 32, The catch block in MyPageViewModel leaves isLoading true
on errors causing infinite loading; update the error path so isLoading is set to
false after fetchUserInfoUseCase.execute() fails — either add isLoading = false
inside the catch that currently calls CherrishLogger.error(error), or refactor
the async call in the function (the method containing
fetchUserInfoUseCase.execute()) to use a defer that sets isLoading = false to
guarantee it is cleared on both success and failure.

Comment on lines +10 to +15
struct CherrishLoadingView: View {
var body: some View {
ProgressView()
.progressViewStyle(.circular)
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

로딩 뷰가 화면 중앙에 배치되도록 frame 설정을 고려해주세요.

현재 구현은 부모 뷰의 레이아웃에 따라 로딩 인디케이터 위치가 달라질 수 있습니다. 전체 화면을 차지하고 중앙에 배치되도록 설정하면 일관된 UX를 제공할 수 있습니다.

♻️ 개선 제안
 struct CherrishLoadingView: View {
     var body: some View {
         ProgressView()
             .progressViewStyle(.circular)
+            .frame(maxWidth: .infinity, maxHeight: .infinity)
     }
 }
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishLoadingView.swift`
around lines 10 - 15, 현재 CherrishLoadingView의 ProgressView가 부모 레이아웃에 따라 위치가 달라질
수 있으니 CherrishLoadingView의 body(ProgressView)를 화면 전체를 차지하도록 확장하고 중앙에 정렬되게 변경하세요;
구체적으로 struct CherrishLoadingView의 body에서 ProgressView를 감싸는 컨테이너(예: ZStack 또는
VStack)를 사용하거나 ProgressView에 frame(maxWidth: .infinity, maxHeight: .infinity) 및
.frame 정렬/alignment를 적용하여 수평·수직 중앙 정렬을 보장하도록 수정하세요.

@y-eonee y-eonee force-pushed the fix/#172-로딩뷰 branch from e642ac6 to 4a50487 Compare January 23, 2026 08:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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/View/Treatment/TreatmentFilterView.swift (1)

73-76: 중첩된 Task를 제거하고 .task의 비동기 컨텍스트에서 직접 호출하세요

.task 내부에 새로운 Task { }를 생성하면 구조화되지 않은 최상위 작업이 만들어져 .task의 취소 신호를 받지 않습니다. 뷰가 제거되어도 작업이 계속 실행될 수 있으므로, 작업을 .task 클로저 내에 직접 유지하세요.

♻️ 수정 제안
.task {
-    Task {
-        try await viewModel.fetchTreatments()
-    }
+    try? await viewModel.fetchTreatments()
}
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/NoTreatment/NoTreatmentViewModel.swift`:
- Around line 73-81: The fetchCategories() method sets isLoading = true but only
clears it on the success path, so failures leave the loading state stuck; change
the method to set isLoading = true then immediately use a defer { isLoading =
false } so isLoading is cleared on both success and error (leave the await call
to fetchCategoriesUseCase.execute() and the catch logging as-is). Apply the same
pattern to the analogous function in the 84-93 range so every async fetch in
this ViewModel uses defer to always reset isLoading.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift`:
- Around line 65-74: The fetchTreatments() method sets isLoading = true but only
clears it on the success path; wrap the cleanup in a defer to ensure isLoading =
false runs on both success and failure: set isLoading = true at the start of
fetchTreatments(), immediately add defer { isLoading = false }, then remove the
manual isLoading = false in the do block; keep the existing treatments
assignment from fetchTreatmentsUseCase.execute(id: nil, keyword: searchText) and
the catch block that sets treatments = [] and logs via
CherrishLogger.network(error) so failures still clear data and are logged.

Comment on lines 73 to 81
@MainActor
func fetchCategories() async {
isLoading = true
do {
categories = try await fetchCategoriesUseCase.execute()
isLoading = false
} catch {
CherrishLogger.debug(error)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

에러 경로에서도 isLoading을 반드시 해제하세요.

현재 catch에서 isLoading = false가 없어 실패 시 로딩 뷰가 영구 표시됩니다. defer로 성공/실패 모두에서 해제되도록 보장해주세요.

🔧 제안 수정(diff)
`@MainActor`
func fetchCategories() async {
    isLoading = true
+   defer { isLoading = false }
    do {
        categories = try await fetchCategoriesUseCase.execute()
-       isLoading = false
    } catch {
        CherrishLogger.debug(error)
    }
}

`@MainActor`
func fetchNoTreatments() async {
    isLoading = true
+   defer { isLoading = false }
    do {
        treatments = try await fetchTreatmentsUseCase.execute(id: selectedCategory?.id, keyword: "")
-       isLoading = false
    } catch {
        treatments = []
        CherrishLogger.debug(error)
    }
}

Also applies to: 84-93

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/NoTreatment/NoTreatmentViewModel.swift`
around lines 73 - 81, The fetchCategories() method sets isLoading = true but
only clears it on the success path, so failures leave the loading state stuck;
change the method to set isLoading = true then immediately use a defer {
isLoading = false } so isLoading is cleared on both success and error (leave the
await call to fetchCategoriesUseCase.execute() and the catch logging as-is).
Apply the same pattern to the analogous function in the 84-93 range so every
async fetch in this ViewModel uses defer to always reset isLoading.

Comment on lines 65 to 74
@MainActor
func fetchTreatments() async throws {
isLoading = true
do {
treatments = try await fetchTreatmentsUseCase.execute(id: nil, keyword: searchText)
isLoading = false
} catch {
treatments = []
CherrishLogger.network(error)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

에러 경로에서 로딩이 해제되지 않습니다.
실패 시 isLoading이 false로 돌아오지 않아 로딩 뷰가 고정될 수 있습니다. 성공/실패 모두에서 해제되도록 defer로 정리하세요.

🔧 수정 제안
`@MainActor`
func fetchTreatments() async throws {
    isLoading = true
+   defer { isLoading = false }
    do {
        treatments = try await fetchTreatmentsUseCase.execute(id: nil, keyword: searchText)
-       isLoading = false
    } catch {
        treatments = []
        CherrishLogger.network(error)
    }
}
📝 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.

Suggested change
@MainActor
func fetchTreatments() async throws {
isLoading = true
do {
treatments = try await fetchTreatmentsUseCase.execute(id: nil, keyword: searchText)
isLoading = false
} catch {
treatments = []
CherrishLogger.network(error)
}
`@MainActor`
func fetchTreatments() async throws {
isLoading = true
defer { isLoading = false }
do {
treatments = try await fetchTreatmentsUseCase.execute(id: nil, keyword: searchText)
} catch {
treatments = []
CherrishLogger.network(error)
}
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift`
around lines 65 - 74, The fetchTreatments() method sets isLoading = true but
only clears it on the success path; wrap the cleanup in a defer to ensure
isLoading = false runs on both success and failure: set isLoading = true at the
start of fetchTreatments(), immediately add defer { isLoading = false }, then
remove the manual isLoading = false in the do block; keep the existing
treatments assignment from fetchTreatmentsUseCase.execute(id: nil, keyword:
searchText) and the catch block that sets treatments = [] and logs via
CherrishLogger.network(error) so failures still clear data and are logged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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/View/Treatment/TreatmentFilterView.swift (1)

82-86: .task 내부에 중복된 Task가 있습니다.

.task 수정자는 이미 async 컨텍스트를 제공하므로 내부에 Task를 중첩할 필요가 없습니다. 중첩된 Task는 에러 처리를 복잡하게 만들고, try로 던져진 에러가 처리되지 않습니다.

🐛 수정 제안
         .task {
-            Task {
-                try await viewModel.fetchTreatments()
-            }
+            do {
+                try await viewModel.fetchTreatments()
+            } catch {
+                // 에러 처리 필요
+            }
         }
🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/Treatment/TreatmentFilterView.swift`:
- Around line 61-74: The horizontal padding modifier is applied to the ForEach
container instead of each row; remove `.padding(.horizontal, 24.adjustedW)` from
the ForEach and move it onto each TreatmentRowView instance so each row gets the
padding (update the TreatmentRowView call inside the ForEach to append
`.padding(.horizontal, 24.adjustedW)`). Keep the existing TreatmentRowView
parameters (displayMode, treatmentEntity, isSelected, action) and ensure the
viewModel methods (`isSelected`, `addTreatment`, `removeTreatment`) continue to
be used as before.
♻️ Duplicate comments (11)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/MyPage/MyPageViewModel.swift (1)

23-32: 에러 경로에서도 isLoading 해제가 필요합니다.

현재 실패 시 isLoading이 true로 남아 무한 로딩이 될 수 있습니다. 성공/실패 공통으로 해제되도록 defer를 권장합니다.

🔧 수정 제안
`@MainActor`
func fetchUserInfo() async throws {
    isLoading = true
+   defer { isLoading = false }

    do {
        let response = try await fetchUserInfoUseCase.execute()
-       isLoading = false
        name = response.name
        day = response.daysSinceSignup
    } catch {
        CherrishLogger.error(error)
    }
}
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/Treatment/TreatmentViewModel.swift (1)

65-74: 에러 경로에서 isLoading 해제가 누락됩니다.

실패 시 로딩 뷰가 고정될 수 있으니 defer로 정리하세요.

🔧 수정 제안
`@MainActor`
func fetchTreatments() async throws {
    isLoading = true
+   defer { isLoading = false }
    do {
        treatments = try await fetchTreatmentsUseCase.execute(id: nil, keyword: searchText)
-       isLoading = false
    } catch {
        treatments = []
        CherrishLogger.network(error)
    }
}
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Home/HomeView.swift (1)

27-29: 주입받는 ViewModel은 @ObservedObject가 맞습니다.

HomeContentView는 부모로부터 viewModel을 전달받으므로 @StateObject 사용 시 소유권 충돌/라이프사이클 문제가 발생할 수 있습니다. 또한 SwiftLint 경고(private_swiftui_state)를 피하려면 private 지정이 필요합니다.

🔧 수정 제안
private struct HomeContentView: View {
-    `@StateObject` var viewModel: HomeViewModel
+    `@ObservedObject` private var viewModel: HomeViewModel
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/ViewModel/NoTreatment/NoTreatmentViewModel.swift (1)

73-92: 에러 경로에서도 로딩 해제가 필요합니다.

두 fetch 함수 모두 실패 시 isLoading이 true로 남아 로딩이 고정될 수 있습니다. defer로 공통 해제를 보장하세요.

🔧 수정 제안
`@MainActor`
func fetchCategories() async {
    isLoading = true
+   defer { isLoading = false }
    do {
        categories = try await fetchCategoriesUseCase.execute()
-       isLoading = false
    } catch {
        CherrishLogger.debug(error)
    }
}

`@MainActor`
func fetchNoTreatments() async {
    isLoading = true
+   defer { isLoading = false }
    do {
        treatments = try await fetchTreatmentsUseCase.execute(id: selectedCategory?.id, keyword: "")
-       isLoading = false
    } catch {
        treatments = []
        CherrishLogger.debug(error)
    }
}
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarViewModel.swift (3)

113-119: 에러 발생 시 isLoadingfalse로 설정되지 않아 무한 로딩 상태가 됩니다.

catch 블록에서 isLoading = false가 누락되어 있습니다. 네트워크 오류 발생 시 사용자는 영원히 로딩 화면을 보게 됩니다.

🐛 수정 제안
         do {
             let response = try await fetchProcedureCountOfMonthUseCase.execute(year: year, month: month)
-            isLoading = false
             procedureCountOfMonth = response.dailyProcedureCounts
         } catch {
             CherrishLogger.error(error)
         }
+        isLoading = false

127-132: 동일한 이슈: 에러 시 isLoading이 초기화되지 않습니다.

🐛 수정 제안
         do {
             procedureList = try await fetchTodayProcedureListUseCase.execute(date: treatmentDate)
-            isLoading = false
         } catch {
             CherrishLogger.error(error)
         }
+        isLoading = false

139-146: 동일한 이슈: 에러 시 isLoading이 초기화되지 않습니다.

🐛 수정 제안
         do {
             let downtimeList = try await fetchProcedureDowntimeUseCase.execute(id: procedureId)
-            isLoading = false 
             selectedDowntime = downtimeList
             mapToDowntimeDays(procedure: downtimeList)
         } catch {
             CherrishLogger.error(error)
         }
+        isLoading = false
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeProgressView.swift (1)

30-33: @StateObject 대신 @ObservedObject를 사용해야 합니다.

ChallengeProgressContentView는 부모 뷰(ChallengeProgressView)로부터 이미 생성된 viewModel을 전달받습니다. @StateObject는 뷰가 해당 객체의 소유권을 가지고 생성할 때 사용하고, 외부에서 전달받는 경우에는 @ObservedObject를 사용해야 합니다.

🐛 수정 제안
 private struct ChallengeProgressContentView: View {
     `@EnvironmentObject` private var challengeCoordinator: ChallengeCoordinator
-    `@StateObject` var viewModel: ChallengeProgressViewModel
+    `@ObservedObject` var viewModel: ChallengeProgressViewModel
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift (3)

26-29: SwiftUI state 속성에 private 접근 제어자를 추가하세요.

SwiftLint 경고에 따르면, SwiftUI의 @StateObject@State 속성은 private으로 선언해야 합니다.

♻️ 수정 제안
 struct CalendarView: View {
     `@EnvironmentObject` private var calendarCoordinator: CalendarCoordinator
-    `@StateObject` var viewModel: CalendarViewModel
-    `@StateObject` var homeCalendarFlowState: HomeCalendarFlowState
-    `@State` var calendarMode: CalendarMode = .none
-    `@State` var selectedProcedureID: Int? = nil
+    `@StateObject` private var viewModel: CalendarViewModel
+    `@StateObject` private var homeCalendarFlowState: HomeCalendarFlowState
+    `@State` private var calendarMode: CalendarMode = .none
+    `@State` private var selectedProcedureID: Int? = nil

44-53: 순차적 fetch 호출로 인한 로딩 상태 깜빡임(flicker) 가능성이 있습니다.

fetchProcedureCountsOfMonthfetchTodayProcedureList 모두 내부에서 isLoading = true로 설정 후 완료 시 false로 설정합니다. 순차 호출 시 true → false → true → false 순서로 변경되어 로딩 뷰가 깜빡일 수 있습니다.

ViewModel에 두 작업을 래핑하는 단일 메서드를 추가하거나, 병렬 실행을 고려하세요.


71-72: @StateObject 대신 @ObservedObject를 사용해야 합니다.

CalendarContentView는 부모 뷰(CalendarView)로부터 viewModelhomeCalendarFlowState를 전달받습니다. 외부에서 전달받는 객체는 @ObservedObject를 사용해야 합니다.

🐛 수정 제안
 private struct CalendarContentView: View {
     `@EnvironmentObject` private var calendarCoordinator: CalendarCoordinator
-    `@StateObject` var viewModel: CalendarViewModel
-    `@StateObject` var homeCalendarFlowState: HomeCalendarFlowState
+    `@ObservedObject` var viewModel: CalendarViewModel
+    `@ObservedObject` var homeCalendarFlowState: HomeCalendarFlowState

Comment on lines +61 to +74
ForEach(viewModel.treatments, id: \.id) { treatment in
TreatmentRowView(
displayMode: .checkBoxView,
treatmentEntity: treatment,
isSelected: .constant(viewModel.isSelected(treatment)),
action: { if viewModel.isSelected(treatment) {
viewModel.removeTreatment(treatment)
} else {
viewModel.addTreatment(treatment)

} }
)
}
.padding(.horizontal, 24.adjustedW)
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

.padding 수정자가 ForEach에 적용되어 있습니다.

ForEach에 적용된 .padding(.horizontal, 24.adjustedW)는 각 행이 아닌 컨테이너에 적용됩니다. NoTreatmentFilterView.swift에서는 TreatmentRowView에 직접 패딩을 적용하는 패턴을 사용합니다(Line 49: .padding(.horizontal, 25.adjustedW)). 일관성을 위해 동일한 패턴을 적용하세요.

♻️ 수정 제안
                         ForEach(viewModel.treatments, id: \.id) { treatment in
                             TreatmentRowView(
                                 displayMode: .checkBoxView,
                                 treatmentEntity: treatment,
                                 isSelected: .constant(viewModel.isSelected(treatment)),
                                 action: {  if viewModel.isSelected(treatment) {
                                     viewModel.removeTreatment(treatment)
                                 } else {
                                     viewModel.addTreatment(treatment)
                                     
                                 } }
                             )
+                            .padding(.horizontal, 24.adjustedW)
                         }
-                        .padding(.horizontal, 24.adjustedW)
📝 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.

Suggested change
ForEach(viewModel.treatments, id: \.id) { treatment in
TreatmentRowView(
displayMode: .checkBoxView,
treatmentEntity: treatment,
isSelected: .constant(viewModel.isSelected(treatment)),
action: { if viewModel.isSelected(treatment) {
viewModel.removeTreatment(treatment)
} else {
viewModel.addTreatment(treatment)
} }
)
}
.padding(.horizontal, 24.adjustedW)
ForEach(viewModel.treatments, id: \.id) { treatment in
TreatmentRowView(
displayMode: .checkBoxView,
treatmentEntity: treatment,
isSelected: .constant(viewModel.isSelected(treatment)),
action: { if viewModel.isSelected(treatment) {
viewModel.removeTreatment(treatment)
} else {
viewModel.addTreatment(treatment)
} }
)
.padding(.horizontal, 24.adjustedW)
}
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/Treatment/TreatmentFilterView.swift`
around lines 61 - 74, The horizontal padding modifier is applied to the ForEach
container instead of each row; remove `.padding(.horizontal, 24.adjustedW)` from
the ForEach and move it onto each TreatmentRowView instance so each row gets the
padding (update the TreatmentRowView call inside the ForEach to append
`.padding(.horizontal, 24.adjustedW)`). Keep the existing TreatmentRowView
parameters (displayMode, treatmentEntity, isSelected, action) and ensure the
viewModel methods (`isSelected`, `addTreatment`, `removeTreatment`) continue to
be used as before.

Copy link
Contributor

@wotjs020708 wotjs020708 left a comment

Choose a reason for hiding this comment

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

어푸루푸푸

Copy link
Contributor

@soseoyo12 soseoyo12 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~

Copy link
Contributor

@sum130 sum130 left a comment

Choose a reason for hiding this comment

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

수고하셨습니당 어푸푸!!

Comment on lines +113 to +119
do {
let response = try await fetchProcedureCountOfMonthUseCase.execute(year: year, month: month)
isLoading = false
procedureCountOfMonth = response.dailyProcedureCounts
} catch {
CherrishLogger.error(error)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

이고 한 번 화긴해주세용!

Copy link
Contributor

Choose a reason for hiding this comment

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

기기대응! 해주세용

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영!!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/Treatment/TreatmentFilterView.swift`:
- Around line 54-56: The loading view is currently inside the ScrollView so user
can still interact while viewModel.isLoading; change the layout to present
CherrishLoadingView as a full-screen overlay and disable interactions on the
underlying ScrollView when viewModel.isLoading. Locate the ScrollView and wrap
content in a ZStack (or apply .disabled(viewModel.isLoading) /
.allowsHitTesting(!viewModel.isLoading) to the ScrollView) so touches are
blocked, and also ensure any trigger methods check viewModel.isLoading (e.g.,
search/fetch handlers) to avoid duplicate calls while loading.

In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeProgressView.swift`:
- Around line 14-26: The view uses .task in ChallengeProgressView which calls
await viewModel.loadChallenge() every time the view appears, risking duplicate
loads on re-entry; if repeated loads are not intended, add a one-time load guard
(e.g., a didLoad Bool on the viewModel or ChallengeProgressView) and check it
before calling loadChallenge(), set the flag after the first successful load,
and update ChallengeProgressContentView logic to rely on
viewModel.isLoading/didLoad to prevent re-entry; alternatively, move the load
call to an initializer or a higher-level lifecycle controller if you want
strictly one-time loading.
♻️ Duplicate comments (3)
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/CalendarMain/CalendarView.swift (2)

26-29: SwiftUI 상태 프로퍼티는 private로 캡슐화하세요.

SwiftLint 경고 대상이며 내부 상태 노출을 줄일 수 있습니다.

♻️ 권장 수정안
-    `@StateObject` var viewModel: CalendarViewModel
-    `@StateObject` var homeCalendarFlowState: HomeCalendarFlowState
-    `@State` var calendarMode: CalendarMode = .none
-    `@State` var selectedProcedureID: Int? = nil
+    `@StateObject` private var viewModel: CalendarViewModel
+    `@StateObject` private var homeCalendarFlowState: HomeCalendarFlowState
+    `@State` private var calendarMode: CalendarMode = .none
+    `@State` private var selectedProcedureID: Int? = nil

44-52: 순차 fetch로 로딩 깜빡임과 부분 실패 위험이 있습니다.

두 메서드가 각각 isLoading을 토글하면 true→false→true로 깜빡이고, 첫 호출이 실패하면 두 번째가 실행되지 않습니다. ViewModel에서 단일 로딩 제어 + 병렬/통합 호출로 묶어 주세요.

🔧 권장 수정안(예시)
         .task (id: viewModel.currentMonth){
             if calendarMode == .none {
                 do {
-                    try await viewModel.fetchProcedureCountsOfMonth()
-                    try await viewModel.fetchTodayProcedureList()
+                    try await viewModel.fetchMonthData()
                 } catch {
                     CherrishLogger.error(error)
                 }
             }
         }
Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/Treatment/TreatmentFilterView.swift (1)

62-75: 행 패딩 적용 위치 일관화 필요
이전 리뷰에서 이미 지적된 항목과 동일합니다. ForEach가 아니라 각 TreatmentRowView에 패딩을 적용하는 쪽이 의도에 더 맞습니다.

Comment on lines +54 to 56
if viewModel.isLoading {
CherrishLoadingView()
} else {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

로딩 중 인터랙션 차단 여부를 확인해 주세요.
현재 로딩 뷰가 ScrollView 내부에만 표시되어 검색/스크롤이 계속 가능합니다. 로딩이 중복 호출 방지 목적이라면 비활성화를 고려해 주세요.

♻️ 제안 (로딩 중 입력 차단)
-            ScrollView(.vertical, showsIndicators: false) {
+            ScrollView(.vertical, showsIndicators: false) {
                 ...
             }
+            .disabled(viewModel.isLoading)
🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/View/Treatment/TreatmentFilterView.swift`
around lines 54 - 56, The loading view is currently inside the ScrollView so
user can still interact while viewModel.isLoading; change the layout to present
CherrishLoadingView as a full-screen overlay and disable interactions on the
underlying ScrollView when viewModel.isLoading. Locate the ScrollView and wrap
content in a ZStack (or apply .disabled(viewModel.isLoading) /
.allowsHitTesting(!viewModel.isLoading) to the ScrollView) so touches are
blocked, and also ensure any trigger methods check viewModel.isLoading (e.g.,
search/fetch handlers) to avoid duplicate calls while loading.

Comment on lines +14 to +26
var body: some View {
ZStack {
if viewModel.isLoading {
CherrishLoadingView()
}
else {
ChallengeProgressContentView(viewModel: viewModel)
}
}
.task {
await viewModel.loadChallenge()
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

로딩 재진입 시 중복 로드 의도 확인 필요

.task는 뷰가 다시 나타날 때마다 실행되어 loadChallenge()가 재호출될 수 있습니다. 이 동작이 의도된 것인지 확인해 주세요. 아니라면 1회 로드 가드(didLoad 플래그 등)를 고려하는 편이 안전합니다.

🤖 Prompt for AI Agents
In
`@Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/View/ChallengeProgressView.swift`
around lines 14 - 26, The view uses .task in ChallengeProgressView which calls
await viewModel.loadChallenge() every time the view appears, risking duplicate
loads on re-entry; if repeated loads are not intended, add a one-time load guard
(e.g., a didLoad Bool on the viewModel or ChallengeProgressView) and check it
before calling loadChallenge(), set the flag after the first successful load,
and update ChallengeProgressContentView logic to rely on
viewModel.isLoading/didLoad to prevent re-entry; alternatively, move the load
call to an initializer or a higher-level lifecycle controller if you want
strictly one-time loading.

@y-eonee y-eonee merged commit 89b67b7 into develop Jan 23, 2026
1 check was pending
@y-eonee y-eonee deleted the fix/#172-로딩뷰 branch January 23, 2026 10:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Refactor 코드 리팩토링 시 사용 나연🐹

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix] 로딩뷰

4 participants

Comments