Skip to content

[Fix] 홈 화면에서의 루틴 등록 및 완료 관련 버그 수정#87

Merged
choijungp merged 3 commits intodevelopfrom
fix/routine-complete
Feb 25, 2026
Merged

[Fix] 홈 화면에서의 루틴 등록 및 완료 관련 버그 수정#87
choijungp merged 3 commits intodevelopfrom
fix/routine-complete

Conversation

@choijungp
Copy link
Contributor

@choijungp choijungp commented Feb 24, 2026

🌁 Background

홈 화면에서 발생되는 루틴 관련 오류를 수정했어요 ~~

📱 Screenshot

1. 루틴 등록 후, 홈 화면에서 바로 표시되도록 수정

Before

ScreenRecording_02-24-2026.19-45-01_1.MP4
  • 기존에는 루틴 등록 후 홈 화면에 바로 표시되지 않아 다른 화면을 다녀와야 했음

After

ScreenRecording_02-24-2026.19-47-40_1.MP4

2. 루틴 완료 표시가 일관성을 유지하도록 수정

Before

ScreenRecording_02-24-2026.19-48-07_1.MP4
  • 기존에는 루틴 완료 표시가 일관성을 유지하지 못하였음

After

ScreenRecording_02-24-2026.19-45-34_1.MP4

3. 홈 화면의 pull to refresh 기능 추가

After

ScreenRecording_02-24-2026.19-46-28_1.MP4
  • 기존에는 없던 기능, pull to refresh 기능을 통해 루틴 목록을 재로드

👩‍💻 Contents

  • 루틴 등록 후, 홈 화면에서 바로 표시되도록 수정
  • 루틴 완료 표시가 일관성을 유지하도록 수정
  • 홈 화면의 pull to refresh 기능 추가

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 루틴 목록에서 당겨서 새로고침(Pull-to-Refresh) 기능 추가
  • 개선 사항

    • 루틴 생성 결과 처리 로직 개선으로 더 안정적인 사용자 경험 제공

@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

요약

날짜 뷰의 updateAllCompleted() 메서드에 isCompleted 파라미터를 추가하고, 홈 뷰컨트롤러에 풀-투-리프레시 기능을 추가하며, 루틴 생성 결과를 퍼블리셔를 통해 처리하는 방식으로 변경했습니다.

변경사항

코호트 / 파일 요약
날짜 뷰 API 업데이트
Projects/Presentation/Sources/Home/View/Component/DateView.swift, Projects/Presentation/Sources/Home/View/Component/WeekView.swift
updateAllCompleted() 메서드에 isCompleted: Bool 파라미터 추가. 토글 동작에서 명시적 상태 설정으로 변경. WeekView의 모든 호출 부분을 새로운 서명으로 업데이트.
풀-투-리프레시 기능
Projects/Presentation/Sources/Home/View/HomeViewController.swift
UIRefreshControl을 routineScrollView에 연결하고, 풀-투-리프레시 액션을 pullToRoutineRefresh() 메서드로 구현. 루틴 업데이트 완료 후 refreshing 상태 종료.
루틴 생성 결과 퍼블리셔
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift, Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift
뷰모델에 routineCreationResultPublisher 출력 추가. 루틴 저장 결과를 퍼블리셔를 통해 방출. 뷰컨트롤러의 성공 처리 로직을 퍼블리셔 구독 기반으로 리팩토링.

예상 코드 리뷰 노력

🎯 3 (중간) | ⏱️ ~20분

🐰 파라미터 추가에 풀-투-리프레시까지,
퍼블리셔 물결 타고 흐르는 결과여,
숨겨진 아이콘 밝혀지고,
루틴 생성은 우아하게 반응하네,
변화의 춤을 춘답니다! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 PR의 주요 변경 사항인 홈 화면의 루틴 등록 및 완료 버그 수정을 명확하게 요약하고 있으며, 변경 사항과 일치합니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/routine-complete

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.

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

🧹 Nitpick comments (1)
Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift (1)

308-314: false 방출값이 사용되지 않음 — 타입 재고 또는 제거 고려

routineCreationResultSubject.send(false)는 에러 시 방출되지만, 구독자(RoutineCreationViewController)는 if creationResult { ... } 조건으로 false를 완전히 무시합니다. 에러 피드백은 이미 networkErrorPublisher를 통해 처리됩니다. false 방출이 의도된 기능 없이 미래 구독자에게 혼란을 줄 수 있습니다.

♻️ 개선 방향 제안

성공만 시그널링하도록 Void 퍼블리셔로 변경하거나, 또는 false를 완전히 제거:

-let routineCreationResultPublisher: AnyPublisher<Bool, Never>
+let routineCreationResultPublisher: AnyPublisher<Void, Never>
-private let routineCreationResultSubject = PassthroughSubject<Bool, Never>()
+private let routineCreationResultSubject = PassthroughSubject<Void, Never>()
-routineCreationResultPublisher: routineCreationResultSubject.eraseToAnyPublisher(),
+routineCreationResultPublisher: routineCreationResultSubject.eraseToAnyPublisher(),
 try await routineUseCase.saveRoutine(routine: routine)
-routineCreationResultSubject.send(true)
+routineCreationResultSubject.send()
 networkRetryHandler.clearRetryState()
 } catch {
-routineCreationResultSubject.send(false)
 networkRetryHandler.handleNetworkError(error) { [weak self] in

ViewController의 구독도 맞게 수정:

-.sink { [weak self] creationResult in
-    guard let self else { return }
-    if creationResult {
+.sink { [weak self] in
+    guard let self else { return }
+    do {
         ...
-    }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift`
around lines 308 - 314, routineCreationResultSubject currently sends false on
error but subscribers (RoutineCreationViewController) ignore false, causing
redundant/ confusing emissions; change routineCreationResultSubject to emit only
success (convert it to a Void/Empty-value publisher or remove the false send) by
deleting the routineCreationResultSubject.send(false) in the catch block and
adjusting the subject's type where it's declared (routineCreationResultSubject)
and any places that subscribe to it in RoutineCreationViewController to handle
only a success signal; keep existing
networkRetryHandler.handleNetworkError(error){ [weak self] in
self?.registerRoutine() } and leave networkErrorPublisher usage unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Projects/Presentation/Sources/Home/View/HomeViewController.swift`:
- Around line 443-444: `routinesPublisher`가 방출되지 않을 때(네트워크 실패)
`routineScrollView.refreshControl?.endRefreshing()`가 호출되지 않아 스피너가 멈추지 않으므로,
`bind()` 내부에서 `networkErrorPublisher`에 추가 구독을 만들어 네트워크 오류 수신 시
`routineScrollView.refreshControl?.endRefreshing()`와 `hideIndicatorView()`를
호출하도록 처리하세요; 또는 `bindNetworkError`의 구현을 확장해 오류 콜백에 위 두 호출을 위임하도록 변경하여
`refreshDailyRoutine` 실패 경로에서도 항상 리프레시가 종료되게 만드세요 (참조:
routineScrollView.refreshControl?.endRefreshing(), routinesPublisher,
networkErrorPublisher, bind(), bindNetworkError, refreshDailyRoutine).

In
`@Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift`:
- Around line 319-322: The toast shown on completion uses
viewModel.action(input: .showUpdateRoutineToastMessageView) regardless of
whether a routine was created or updated; change the branch in
RoutineCreationViewController (the isFromMypage == false path) to check
updateInfo == nil and call a distinct action for creation (e.g.,
.showCreateRoutineToastMessageView) when new, and only call
.showUpdateRoutineToastMessageView when updateInfo != nil; if the
ToastAction/Action enum or viewModel lacks a create case, add
.showCreateRoutineToastMessageView and implement handling where Toast actions
are consumed so the correct "생성" vs "수정" message is displayed, then keep the
existing popViewController(animated: true) behavior.

---

Nitpick comments:
In
`@Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift`:
- Around line 308-314: routineCreationResultSubject currently sends false on
error but subscribers (RoutineCreationViewController) ignore false, causing
redundant/ confusing emissions; change routineCreationResultSubject to emit only
success (convert it to a Void/Empty-value publisher or remove the false send) by
deleting the routineCreationResultSubject.send(false) in the catch block and
adjusting the subject's type where it's declared (routineCreationResultSubject)
and any places that subscribe to it in RoutineCreationViewController to handle
only a success signal; keep existing
networkRetryHandler.handleNetworkError(error){ [weak self] in
self?.registerRoutine() } and leave networkErrorPublisher usage unchanged.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0973070 and 0278f0b.

📒 Files selected for processing (5)
  • Projects/Presentation/Sources/Home/View/Component/DateView.swift
  • Projects/Presentation/Sources/Home/View/Component/WeekView.swift
  • Projects/Presentation/Sources/Home/View/HomeViewController.swift
  • Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift
  • Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift

Comment on lines +443 to 444
self?.routineScrollView.refreshControl?.endRefreshing()
self?.hideIndicatorView()
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

네트워크 오류 시 refreshControl이 종료되지 않아 스피너가 무한히 표시됨

endRefreshing()routinesPublisher 구독 내에서만 호출됩니다. refreshDailyRoutine 액션이 네트워크 오류로 실패하면:

  1. networkErrorPublisher로 에러 알럿이 표시됨
  2. routinesPublisher는 방출되지 않음
  3. endRefreshing()이 호출되지 않아 리프레시 스피너가 무한히 계속 돌아감

networkErrorPublisher를 처리하는 경로에서도 endRefreshing()을 호출해야 합니다.

🐛 수정 방향 제안

bind() 내에서 networkErrorPublisher에 추가 구독을 연결하거나, bindNetworkError 호출 전에 별도 처리:

+        viewModel.output.networkErrorPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [weak self] _ in
+                self?.routineScrollView.refreshControl?.endRefreshing()
+            }
+            .store(in: &cancellables)
+
         bindNetworkError(from: viewModel.output.networkErrorPublisher)

또는 bindNetworkError 구현 내부에서 콜백으로 처리하는 방식도 가능합니다.

📝 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
self?.routineScrollView.refreshControl?.endRefreshing()
self?.hideIndicatorView()
viewModel.output.networkErrorPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.routineScrollView.refreshControl?.endRefreshing()
}
.store(in: &cancellables)
bindNetworkError(from: viewModel.output.networkErrorPublisher)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Presentation/Sources/Home/View/HomeViewController.swift` around
lines 443 - 444, `routinesPublisher`가 방출되지 않을 때(네트워크 실패)
`routineScrollView.refreshControl?.endRefreshing()`가 호출되지 않아 스피너가 멈추지 않으므로,
`bind()` 내부에서 `networkErrorPublisher`에 추가 구독을 만들어 네트워크 오류 수신 시
`routineScrollView.refreshControl?.endRefreshing()`와 `hideIndicatorView()`를
호출하도록 처리하세요; 또는 `bindNetworkError`의 구현을 확장해 오류 콜백에 위 두 호출을 위임하도록 변경하여
`refreshDailyRoutine` 실패 경로에서도 항상 리프레시가 종료되게 만드세요 (참조:
routineScrollView.refreshControl?.endRefreshing(), routinesPublisher,
networkErrorPublisher, bind(), bindNetworkError, refreshDailyRoutine).

Comment on lines +319 to +322
} else {
viewModel.action(input: .showUpdateRoutineToastMessageView)
self.navigationController?.popViewController(animated: true)
}
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

🧩 Analysis chain

🏁 Script executed:

# Search for showUpdatedRoutineToast usage and related message display
rg -n "showUpdatedRoutineToast" --type swift -A 5

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 2898


🏁 Script executed:

# Search for Korean text related to routine update/creation toast message
rg -n "수정|생성|등록" --type swift | grep -i toast

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 212


🏁 Script executed:

# Search for ToastMessage or toast-related views
rg -n "ToastMessage|ToastView" --type swift

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 4523


🏁 Script executed:

# Check RoutineCreationViewController to see if there's different handling for new vs update
cat -n Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift | sed -n '305,330p'

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 1546


🏁 Script executed:

# Check if there's a separate toast message for routine creation (vs update)
rg -n "루틴.*등록|루틴.*생성|routine.*creat" --type swift -i | head -20

Repository: YAPP-Github/Bitnagil-iOS

Length of output: 3256


루틴 신규 생성 시 토스트 메시지가 "수정" 관련 문구로 표시되는 문제

isFromMypage == false 분기(라인 320)에서 호출하는 showUpdateRoutineToastMessageView는 RoutineListViewController에서 "루틴 수정이 완료되었습니다." 토스트를 표시합니다. 그런데 이 분기는 신규 루틴 생성(updateInfo == nil)도 포함하므로, 신규 등록 시에 수정 관련 문구가 표시되는 것이 부적절합니다.

신규 생성과 수정을 구분하여 각각 적절한 토스트 메시지("루틴 생성이 완료되었습니다." vs "루틴 수정이 완료되었습니다.")를 표시하도록 수정이 필요합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationViewController.swift`
around lines 319 - 322, The toast shown on completion uses
viewModel.action(input: .showUpdateRoutineToastMessageView) regardless of
whether a routine was created or updated; change the branch in
RoutineCreationViewController (the isFromMypage == false path) to check
updateInfo == nil and call a distinct action for creation (e.g.,
.showCreateRoutineToastMessageView) when new, and only call
.showUpdateRoutineToastMessageView when updateInfo != nil; if the
ToastAction/Action enum or viewModel lacks a create case, add
.showCreateRoutineToastMessageView and implement handling where Toast actions
are consumed so the correct "생성" vs "수정" message is displayed, then keep the
existing popViewController(animated: true) behavior.

@choijungp choijungp merged commit 307b8c9 into develop Feb 25, 2026
2 checks passed
@choijungp choijungp deleted the fix/routine-complete branch February 25, 2026 07:27
@choijungp choijungp self-assigned this Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant