[Feat-T3-178] 루틴 완료 디자인 반영 및 서버 v2 연동#56
Conversation
Walkthrough루틴 완료 데이터 모델과 엔드포인트를 갱신하고, 저장소/도메인 프로토콜 시그니처를 Changes
Sequence Diagram(s)sequenceDiagram
participant U as 사용자
participant H as HomeViewController
participant VM as HomeViewModel
participant UC as RoutineUseCase
participant RP as RoutineRepository
participant API as RoutineEndpoint(Server)
rect rgb(245,248,255)
note over H,VM: 주간 기준 일간 루틴 조회
U->>H: 날짜 선택/주 이동
H->>VM: Input.fetchDailyRoutine
VM->>UC: fetchRoutines(startDate..endDate)
UC->>RP: fetchRoutines(...)
RP->>API: GET /routines?startDate&endDate
API-->>RP: 루틴 목록
RP-->>UC: 루틴 목록
UC-->>VM: 루틴 목록
VM-->>H: Output.dailyRoutinesPublisher
VM-->>H: Output.allCompletedRoutineDatePublisher
H->>WeekView: updateAllCompletedState([Date])
end
sequenceDiagram
participant U as 사용자
participant H as HomeViewController
participant RV as RoutineView
participant VM as HomeViewModel
participant UC as RoutineUseCase
participant RP as RoutineRepository
participant API as RoutineEndpoint(Server)
rect rgb(245,255,245)
note over RV,H: 메인/서브 루틴 완료 토글
U->>RV: 체크 토글(메인/서브)
RV->>H: RoutineViewDelegate.didTap...(Routine)
H->>VM: updateRoutineCompletion(updatedRoutine)
VM->>UC: updateRoutineCompletions([RoutineEntity])
UC->>RP: updateRoutineCompletions([RoutineEntity])
RP->>API: PUT /completions (routineCompleteYn, subRoutineCompleteYn[])
API-->>RP: 200 OK
RP-->>UC: 완료
UC-->>VM: 완료
VM-->>H: Output.updateRoutineCompletionPublisher(성공)
H->>WeekView: updateAllCompletedState([Date]) (갱신 반영)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Projects/Presentation/Sources/Home/View/HomeViewController.swift (1)
468-471: UIStackView에서 arrangedSubviews 제거 방식 수정 필요
removeFromSuperview()만 호출하면 UIStackView의arrangedSubviews배열에는 남아 레이아웃/제약 경고 및 메모리 보유가 발생할 수 있습니다.removeArrangedSubview(_:)와 병행 호출이 필요합니다.- routineStackView.arrangedSubviews.forEach { - $0.removeFromSuperview() - } + routineStackView.arrangedSubviews.forEach { v in + routineStackView.removeArrangedSubview(v) + v.removeFromSuperview() + }Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
204-241: 비동기 경쟁 조건: 캐시 미스 시 ‘즉시 빈 목록 발행’로 인한 깜빡임/오동작 가능
fetchRoutines(startDate:endDate:)는 내부에서Task로 비동기 조회를 시작합니다. 직후에if let dailyRoutines ... else { routinesSubject.send([]) }가 실행되어, 네트워크 응답이 오기 전 빈 목록이 발행될 수 있습니다(플리커링, 잘못된 빈 상태 노출).최소 수정안: 네트워크 조회를 시작한 뒤 조기 반환하고, 실데이터 발행은 조회 완료 시점(VC에서 성공 수신 후
.fetchDailyRoutine재호출 또는 본 VM 내부 로직)으로 일임합니다.fetchRoutines(startDate: startDate, endDate: endDate) - - if let dailyRoutines = routines[date.convertToString(dateType: .yearMonthDate)] { - // 새로 캐싱된 루틴들에서 데이터가 있을 때, - routinesSubject.send(dailyRoutines) - } else { - // 새로 캐싱된 루틴들에서 데이터가 없을 때, - routinesSubject.send([]) - } + // 비동기 조회 완료 후(별도 파이프라인) 발행 + return추가로, 중첩
Task사용을 줄이고fetchRoutines(startDate:endDate:)를async로 변경한 뒤await로 순서를 보장하는 구조로의 리팩터링을 권장합니다.
🧹 Nitpick comments (17)
Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)
72-74: 접근성: 장식용 아이콘은 보조기기에서 숨기세요완료 여부를 시각적으로만 전달하는 장식 요소라면 VoiceOver 노이즈를 줄이기 위해 접근성에서 제외하는 것이 좋습니다.
적용 diff:
allCompletedIcon.image = BitnagilIcon.asteriskIcon allCompletedIcon.isHidden = true + allCompletedIcon.isAccessibilityElement = falseAlso applies to: 99-102
Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift (1)
14-16: DTO 필드 계약 고정: CodingKeys 명시로 리스크 최소화서버 스펙이 정확히 이 키를 요구한다면 명시적인 CodingKeys로 계약을 고정해 두는 편이 안전합니다(리네이밍으로 인한 회귀 방지).
적용 예:
struct RoutineCompletionDTO: Encodable { let routineId: String let routineCompleteYn: Bool let subRoutineCompleteYn: [Bool] + private enum CodingKeys: String, CodingKey { + case routineId, routineCompleteYn, subRoutineCompleteYn + } }
- subRoutineCompleteYn의 길이가 서버가 인지하는 서브루틴 개수와 항상 일치하는지(패딩/누락 없음) 한 번 더 검증 부탁드립니다.
Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift (2)
84-87: updateSubRoutineState는 외부 노출 불필요 — 접근 제한자 축소외부에서 호출될 이유가 없어 보입니다. private으로 감추면 인터페이스가 더 명확해집니다.
적용 diff:
- func updateSubRoutineState() { + private func updateSubRoutineState() { let subRoutineCheckIcon = subRoutine.isDone ? BitnagilIcon.checkedCircleSmallIcon : BitnagilIcon.uncheckedCircleSmallIcon subRoutineCheckButton.setImage(subRoutineCheckIcon, for: .normal) }
25-29: 튜플 기반 상태 대신 명시적 타입 도입 고려(title, isDone) 튜플은 의미 전달이 약합니다. 작은 불변/가변 필드를 갖는 struct로 치환하면 확장성(메모, 식별자 등)과 가독성이 좋아집니다.
예시:
struct SubRoutineItem { let title: String var isDone: Bool } // 사용 private var subRoutine: SubRoutineItem { didSet { updateSubRoutineState() } }Projects/Presentation/Sources/Home/Model/Routine.swift (1)
23-37: 양방향 매핑 전반적으로 타당 · 서브루틴 길이 불일치 가드 추가 권장프로퍼티 매핑이 DTO ↔ Domain ↔ Presentation 간 대칭적으로 보입니다. 다만
subRoutines와subRoutineCompleted의 길이가 달라질 경우 서버와 의미 불일치가 발생합니다. 디버그에서라도 불일치를 조기에 감지하도록assert를 추가하는 것이 안전합니다.아래처럼 간단히 가드를 넣어 주세요.
func toRoutineEntity() -> RoutineEntity { - return RoutineEntity( + assert(subRoutines.count == subRoutineCompleted.count, "subRoutines.count와 subRoutineCompleted.count가 다릅니다.") + return RoutineEntity( routineId: id, routineName: title, repeatDay: repeatDay.map({ $0.rawValue }), executionTime: startTime.convertToString(dateType: .time), routineCompleteYn: isDone, subRoutineNames: subRoutines, subRoutineCompleteYn: subRoutineCompleted, recommendedRoutineType: routineType?.rawValue, routineDeletedYn: isDeleted, routineStartDate: startDate.convertToString(dateType: .yearMonthDate), routineEndDate: endDate.convertToString(dateType: .yearMonthDate)) }소소한 개선:
repeatDay.map({ $0.rawValue })→repeatDay.map(\.rawValue)로 간결화 가능.Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
77-83: 테스트 용이성과 격리성 향상을 위한 의존성 주입 고려
NetworkService.shared싱글톤을 직접 참조하면 단위 테스트가 어려워집니다. 생성자 주입으로NetworkServiceProtocol을 받아 사용하도록 변경하면 목킹이 쉬워집니다.Projects/Presentation/Sources/Home/View/HomeViewController.swift (4)
409-416: 로딩 인디케이터 hide 타이밍 정합성
fetchRoutineResultPublisher에서hideIndicatorView()를 호출하고, 이후.fetchDailyRoutine가 다시 네트워크를 트리거하면 스피너가 너무 일찍 사라질 수 있습니다. 아래 중 한 가지로 정리하는 것을 권장합니다.
- fetch 완료 → daily fetch 시작 전까지 스피너 유지, daily 결과 수신 시 hide
- 혹은 daily fetch는 캐시만 접근하도록 보장하고 스피너는 여기서 hide
434-441: 루틴 완료 후 재조회 흐름은 합리적이나, UX 보완 제안(탭 중 연속 입력 방지)업데이트 성공 시
.refreshDailyRoutine로 재조회하는 흐름은 좋습니다. 다만 업데이트 동안 중복 탭을 방지하고 피드백을 주기 위해, 탭 시점에 스피너를 보여주는 것이 좋습니다. 아래처럼 델리게이트 메서드에서showIndicatorView()를 호출해 주세요.// 아래 변경은 RoutineViewDelegate 구현부(603~622)에서 수행 - viewModel.action(input: .updateRoutineCompletion(updatedRoutine: updatedRoutine)) + self?.showIndicatorView() + viewModel.action(input: .updateRoutineCompletion(updatedRoutine: updatedRoutine))
602-612: 메인 루틴 토글 시 서브루틴 UI 즉시 반영을 보장해 주세요현재 메인 완료 토글 시 모델만 변경되어 서버 반영 후 리프레시까지 서브루틴 UI가 이전 상태로 보일 수 있습니다. RoutineView에서 서브루틴 뷰의 완료 상태를 즉시 동기화하도록 보완하는 것을 권장합니다(아래 RoutineView 코멘트 참고). 또한 탭 시 스피너 표시(위 코멘트) 적용을 권장합니다.
194-199: 플로팅 버튼 토글 시 숨김 처리 타이밍 개선으로 페이드 아웃 애니메이션 살리기현재
toggleFloatingButton()내부에서isHidden을 즉시 토글한 뒤alpha를 애니메이션하고 있어, 숨김 시 페이드 아웃이 보이지 않습니다(isHidden = true이면 그려지지 않음). 숨김 전에는isHidden = false로 두고, 애니메이션 완료 콜백에서 숨김을 반영하세요.아래는
toggleFloatingButton()(라인 555-566) 수정 예시입니다.private func toggleFloatingButton() { floatingButton.toggle() isShowingFloatingMenu.toggle() - floatingMenu.isHidden = !isShowingFloatingMenu - dimmedView.isHidden = !isShowingFloatingMenu - - UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) { - self.dimmedView.alpha = self.isShowingFloatingMenu ? 1 : 0 - self.floatingMenu.alpha = self.isShowingFloatingMenu ? 1 : 0 - } + // 애니메이션 전에는 항상 표시 상태로 두고 alpha로 제어 + floatingMenu.isHidden = false + dimmedView.isHidden = false + UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) { + self.dimmedView.alpha = self.isShowingFloatingMenu ? 1 : 0 + self.floatingMenu.alpha = self.isShowingFloatingMenu ? 1 : 0 + } completion: { _ in + // 완료 후 숨김 반영 + self.floatingMenu.isHidden = !self.isShowingFloatingMenu + self.dimmedView.isHidden = !self.isShowingFloatingMenu + } }Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (4)
19-29: timeLabel 폭 상수(42pt) 재고 권장"하루 종일" 같은 문구가 2줄로 접히며 컴팩트 폭에서는 가독성이 떨어질 수 있습니다. 동적 폭(예: 콘텐츠 Hugging/Compression 조정) 또는 약간 넓은 상수(예: 56~64pt)로 재검토를 권장합니다.
79-88: 메인 완료 토글 동작 자체는 OK, 단 UI 동기화 보완 필요토글 후
self.routine = updatedRoutine→didSet에서 메인 체크 아이콘이 갱신됩니다. 서브루틴의 체크 상태도 즉시 반영되도록updateRoutineState()에서 서브루틴 뷰 상태까지 갱신해 주세요(아래 코멘트 참조).
157-160: updateRoutineState에서 서브루틴 상태도 동기화메인 체크 아이콘만 갱신하고 있어, 모델의
subRoutineCompleted변화가 서브뷰에 즉시 반영되지 않을 수 있습니다.func updateRoutineState() { let isDone = routine.isDone mainRoutineCheckButton.setImage(isDone ? BitnagilIcon.checkedCircleIcon : BitnagilIcon.uncheckedCircleIcon, for: .normal) + // 서브루틴 완료 상태 UI 동기화 + for (index, view) in subRoutineViews { + guard index < routine.subRoutineCompleted.count else { continue } + let completed = routine.subRoutineCompleted[index] + view.setCompleted?(completed) + } }
SubRoutineView에 완료 상태를 반영하는 퍼블릭 API가 없다면 추가 구현이 필요합니다.
163-170: 델리게이트 호출 시 명시적 전달 및 서브뷰 UI 즉시 반영
self.routine = updatedRoutine후delegate?.routineView(self, didTapSubRoutine: routine)는 동작상 문제는 없지만, 가독성을 위해updatedRoutine을 명시적으로 전달해 주세요.- 탭한 서브루틴 뷰의 UI는
SubRoutineView내부에서 이미 토글될 수도 있으나, 일관성을 위해 본 뷰에서도 해당 인덱스의 상태를 즉시 동기화해 주는 것을 권장합니다.- self.routine = updatedRoutine - self.delegate?.routineView(self, didTapSubRoutine: routine) + self.routine = updatedRoutine + // UI 동기화(옵셔널) + subRoutineViews[index]?.setCompleted?(updatedRoutine.subRoutineCompleted[index]) + self.delegate?.routineView(self, didTapSubRoutine: updatedRoutine)Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (3)
137-143: 주 차 이동/주 시작일 계산 로직 OK, 단 캘린더 의존성은 명시화 권장월요일 기준 산출이 명확합니다. 다만
Calendar.current의 지역 설정에 따라 주 시작 요일이 달라질 수 있어(예: 일부 지역은 일요일 시작), ISO8601 캘린더를 명시하거나dateInterval(of:for:)활용을 권장합니다.예시:
var cal = Calendar(identifier: .iso8601) cal.timeZone = .current let weekStart = cal.date(from: cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date)) ?? dateAlso applies to: 145-151
243-251: 재조회 시 순서 보장
refreshSelectedDateRoutines()에서도Task내부에서 다시fetchRoutines(...)가 또 다른Task를 생성합니다. 순서/경쟁을 명확히 하려면fetchRoutines를async로 바꾸고 여기서await한 뒤fetchDailyRoutine/fetchAllCompletedRoutine을 호출하는 것을 권장합니다.
257-263: 업데이트 에러 처리 TODO 정리성공 시
true발행만 있고, 실패 시 TODO가 남아 있습니다. 실패 시에도updateRoutineCompletionResultSubject.send(false)를 발행하거나, 별도의errorPublisher를 통해 UI에서 사용자 메시지를 노출할 수 있게 해 주세요.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (16)
Projects/DataSource/Sources/DTO/RoutineCompletionDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift(1 hunks)Projects/DataSource/Sources/Repository/RoutineRepository.swift(1 hunks)Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift(0 hunks)Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift(1 hunks)Projects/Presentation/Sources/Common/View/TabBarView.swift(1 hunks)Projects/Presentation/Sources/Home/Model/Routine.swift(1 hunks)Projects/Presentation/Sources/Home/View/Component/DateView.swift(4 hunks)Projects/Presentation/Sources/Home/View/Component/RoutineDeleteAlertView.swift(0 hunks)Projects/Presentation/Sources/Home/View/Component/RoutineView.swift(3 hunks)Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift(1 hunks)Projects/Presentation/Sources/Home/View/Component/WeekView.swift(3 hunks)Projects/Presentation/Sources/Home/View/HomeViewController.swift(8 hunks)Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift(8 hunks)
💤 Files with no reviewable changes (2)
- Projects/Presentation/Sources/Home/View/Component/RoutineDeleteAlertView.swift
- Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-16T09:21:15.038Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Applied to files:
Projects/Presentation/Sources/Common/View/TabBarView.swiftProjects/Presentation/Sources/Home/View/HomeViewController.swift
📚 Learning: 2025-08-01T06:38:18.687Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#31
File: Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift:97-103
Timestamp: 2025-08-01T06:38:18.687Z
Learning: choijungp의 HomeViewModel에서 fetchRoutines(for date: Date) 메서드는 기존 캐시 범위를 확장하는 것이 아니라, 요청된 날짜 주변의 새로운 데이터 청크를 가져오기 위해 oldestDate와 latestDate를 모두 업데이트한다. 과거 날짜 요청 시에는 해당 날짜의 1주 전~1일 전, 미래 날짜 요청 시에는 해당 날짜의 1일 후~1주 후 범위로 설정한다.
Applied to files:
Projects/Presentation/Sources/Home/View/HomeViewController.swiftProjects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
📚 Learning: 2025-07-16T09:09:13.869Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Applied to files:
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift
🧬 Code graph analysis (11)
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (2)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
updateRoutineCompletions(77-86)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
updateRoutineCompletions(66-68)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
updateRoutineCompletions(77-86)
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (2)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
updateRoutineCompletions(77-86)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
updateRoutineCompletions(66-68)
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (3)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
createRoutine(13-28)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
createRoutine(50-52)Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (1)
updateRoutineCompletion(254-264)
Projects/Presentation/Sources/Home/Model/Routine.swift (2)
Projects/DataSource/Sources/DTO/RoutineDTO.swift (1)
toRoutineEntity(32-45)Projects/Shared/Sources/Extension/Date+.swift (1)
convertToString(16-22)
Projects/Presentation/Sources/Home/View/Component/WeekView.swift (1)
Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)
updateAllCompleted(127-129)
Projects/Presentation/Sources/Home/View/HomeViewController.swift (3)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (4)
action(76-106)fetchDailyRoutine(205-241)selectDate(153-156)updateRoutineCompletion(254-264)Projects/Presentation/Sources/Home/View/Component/WeekView.swift (2)
updateAllCompletedState(108-115)selectDate(118-122)Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)
selectDate(119-121)
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (2)
Projects/Presentation/Sources/Home/View/HomeViewController.swift (2)
routineView(603-611)routineView(613-622)Projects/Shared/Sources/Extension/Date+.swift (1)
convertToString(16-22)
Projects/DataSource/Sources/Repository/RoutineRepository.swift (1)
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
updateRoutineCompletions(66-68)
Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift (1)
Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (2)
subRoutineView(164-169)configureLayout(95-155)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (4)
Projects/Shared/Sources/Extension/Date+.swift (1)
convertToString(16-22)Projects/DataSource/Sources/Repository/RoutineRepository.swift (2)
fetchRoutines(37-49)updateRoutineCompletions(77-86)Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (2)
fetchRoutines(23-29)updateRoutineCompletions(66-68)Projects/Presentation/Sources/Home/Model/Routine.swift (2)
toRoutine(41-54)toRoutineEntity(24-37)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Home/View/Component/WeekView.swift
[Warning] 111-111: where clauses are preferred over a single if inside a for
(for_where)
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift
[Warning] 199-199: TODOs should be resolved (에러 처리)
(todo)
[Warning] 261-261: TODOs should be resolved (에러 처리)
(todo)
🔇 Additional comments (15)
Projects/Presentation/Sources/Home/View/Component/DateView.swift (1)
33-33: 콜백 이름 변경(didTapDateButton) – 모든 호출부 확인 완료프로젝트 전체를 검색한 결과, 기존 식별자
didTappedDateButton사용 흔적은 없고, 오직 새 이름인didTapDateButton만 사용되고 있음을 확인했습니다.
- DateView.swift: 선언부(var didTapDateButton) 및 호출부(didTapDateButton?(date))
- WeekView.swift: 할당부(dateView.didTapDateButton = { … })
따라서 모든 외부 호출부가 올바르게 갱신되었으니 해당 리뷰 코멘트는 클로즈해도 무방합니다.
Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift (1)
45-49: HTTP 메서드 분리(POST/PUT) 코드 상 확인 완료 — 서버 스펙(v2)과 응답 코드(200/204) 최종 확인 필요
- Projects/DataSource/Sources/Endpoint/RoutineEndpoint.swift
•case .updateRoutineCompletion:→ path"/completions", method.put적용 확인- Projects/DataSource/Sources/Repository/RoutineRepository.swift (라인 85)
•networkService.request(endpoint: endpoint, type: EmptyResponseDTO.self)호출로 빈 바디 응답 처리 확인위와 같이 POST/PUT 매핑 및 EmptyResponseDTO 사용이 코드상 적절히 적용되었습니다. 이제 API 문서 또는 서버 구현을 통해 해당 엔드포인트가 실제로 PUT을 요구하고 204 No Content(또는 빈 바디) 응답을 반환하는지 최종 검증 부탁드립니다.
Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift (1)
47-55: 클로저 캡처 안전 처리 LGTM[weak self] 사용과 상태 동기화(모델 토글 → 뷰 갱신 → 델리게이트 통지) 플로우가 간결하고 명확합니다.
Projects/Presentation/Sources/Common/View/TabBarView.swift (1)
51-51: HomeViewController로 전환 LGTM루트를 VC로 통일하는 방향이 네비게이션 합치에 유리합니다. 의존성 주입도 유지되어 있고 탭 구성과 잘 맞습니다.
Projects/Domain/Sources/Protocol/UseCase/RoutineUseCaseProtocol.swift (1)
21-21: RoutineCompletionEntity 참조 모두 제거됨을 확인했습니다
rg 검색 결과, 더 이상RoutineCompletionEntity에 대한 참조가 없으며,updateRoutineCompletions(routines:)호출부도 정상적으로 반영되어 있습니다. 해당 변경 사항을 승인합니다.Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift (1)
41-41: 검토 결과: Repository 시그니처 및 호출부 일치 확인 · 주석 보강 제안
updateRoutineCompletions(routines: [RoutineEntity])시그니처가 Protocol↔UseCase↔Repository 전반에 걸쳐 일치함을 확인했습니다.- 호출부에서는 모두
RoutineEntity만 전달하고 있으며, 이전 DTO/엔티티 (RoutineCompletionEntity) 사용 흔적이 없습니다.- Repository 구현 내부에서만
RoutineCompletionDTO를 매핑 용도로 사용 중임을 확인했습니다.제안:
Projects/Domain/Sources/Protocol/Repository/RoutineRepositoryProtocol.swift파일에서 해당 메서드 선언 위에 아래와 같은 주석을 추가해 두면, 서버에 실제로 전달되는 필드를 명시하여 오용을 줄일 수 있습니다./// - Note: 서버에는 `routineId`, `routineCompleteYn`, `subRoutineCompleteYn` 필드만 전달됩니다. func updateRoutineCompletions(routines: [RoutineEntity]) async throws이상입니다.
Projects/Domain/Sources/UseCase/Routine/RoutineUseCase.swift (1)
66-68: 단순 위임 처리 LGTMUseCase가 Repository로 안전하게 위임하며 예외 전파도 유지됩니다. 별도 로직이 없어 리스크 낮습니다.
Projects/Presentation/Sources/Home/View/HomeViewController.swift (4)
14-14: HomeView → HomeViewController 전환 LGTMVC로의 전환과 BaseViewController 제네릭 파라미터 적용이 일관적입니다.
230-237: 레이아웃 계층 구성 적절헤더/주차/리스트 영역을 StackView로 구성하고 컨테이너에 배치한 구조가 읽기 쉽습니다.
Also applies to: 234-236, 238-240, 241-243
458-463: 주 단위 전체완료 표시 바인딩 LGTM
WeekView.updateAllCompletedState로 상태를 전달하는 파이프라인이 명확합니다.
613-623: 서브루틴 토글 → 메인 완료 상태 동기화 로직은 타당서브루틴 완료 개수로
isDone을 산출하는 로직이 명확합니다. 상위/하위 상태 간 일관성 보장 측면에서 적절합니다.Projects/Presentation/Sources/Home/View/Component/RoutineView.swift (1)
12-16: 델리게이트 디자인 합리적메인/서브 루틴 탭을 분리한 델리게이트 시그니처가 명확합니다.
Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift (3)
20-21: 일간 조회/리프레시 입력 추가 LGTM입력 분리가 명확하여 흐름을 이해하기 쉽습니다.
33-33: 주간 ‘모두 완료’ 퍼블리셔 추가 및 내부 상태 맵 도입 LGTM
routinesCompleted맵과allCompletedRoutineDatePublisher추가로 표시/상태 전파가 단순해졌습니다.Also applies to: 38-38, 47-47, 72-74
184-202: 배치 조회 후 상태 맵/퍼블리셔 갱신 흐름 명확DTO → Entity → Presentation 모델 변환 및
routinesCompleted/fetchRoutineResultSubject갱신 순서가 적절합니다.
| func updateRoutineCompletions(routines: [RoutineEntity]) async throws { | ||
| let completionDTO = routines.map({ RoutineCompletionDTO( | ||
| routineId: $0.routineId, | ||
| completeYn: $0.completeYn, | ||
| historySeq: $0.historySeq, | ||
| routineType: $0.routineType) }) | ||
|
|
||
| let completionListDTO = RoutineCompletionListDTO( | ||
| performedDate: performedDate, | ||
| routineCompletionInfos: completionDTO) | ||
| routineCompleteYn: $0.routineCompleteYn, | ||
| subRoutineCompleteYn: $0.subRoutineCompleteYn) }) | ||
| let completionListDTO = RoutineCompletionListDTO(routineCompletionInfos: completionDTO) | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
v2 DTO 필드 매핑/배열 정합성 검증 필요
- 서버 스펙 상
routineCompleteYn,subRoutineCompleteYn키명이 정확한지 재검증이 필요합니다(이전 스펙의completeYn와 혼재 위험). subRoutineCompleteYn는 서브루틴 개수와 정확히 동일한 길이여야 합니다. 불일치 시 서버에서 400/422가 날 수 있으니, 전송 전 길이 검증을 권장합니다.
아래처럼 전송 전에 길이 검증과 방어 로깅을 추가해 주세요.
func updateRoutineCompletions(routines: [RoutineEntity]) async throws {
- let completionDTO = routines.map({ RoutineCompletionDTO(
- routineId: $0.routineId,
- routineCompleteYn: $0.routineCompleteYn,
- subRoutineCompleteYn: $0.subRoutineCompleteYn) })
+ // 방어: 서브루틴 이름/완료 여부 배열 길이 정합성 확인
+ try routines.forEach { r in
+ guard r.subRoutineNames.count == r.subRoutineCompleteYn.count else {
+ throw RoutineRepositoryError.invalidSubRoutineLength(
+ routineId: r.routineId,
+ names: r.subRoutineNames.count,
+ completes: r.subRoutineCompleteYn.count
+ )
+ }
+ }
+ let completionDTO = routines.map {
+ RoutineCompletionDTO(
+ routineId: $0.routineId,
+ routineCompleteYn: $0.routineCompleteYn,
+ subRoutineCompleteYn: $0.subRoutineCompleteYn
+ )
+ }필요 시 RoutineRepositoryError를 내부 enum으로 정의해 주세요.
📝 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.
| func updateRoutineCompletions(routines: [RoutineEntity]) async throws { | |
| let completionDTO = routines.map({ RoutineCompletionDTO( | |
| routineId: $0.routineId, | |
| completeYn: $0.completeYn, | |
| historySeq: $0.historySeq, | |
| routineType: $0.routineType) }) | |
| let completionListDTO = RoutineCompletionListDTO( | |
| performedDate: performedDate, | |
| routineCompletionInfos: completionDTO) | |
| routineCompleteYn: $0.routineCompleteYn, | |
| subRoutineCompleteYn: $0.subRoutineCompleteYn) }) | |
| let completionListDTO = RoutineCompletionListDTO(routineCompletionInfos: completionDTO) | |
| func updateRoutineCompletions(routines: [RoutineEntity]) async throws { | |
| // 방어: 서브루틴 이름/완료 여부 배열 길이 정합성 확인 | |
| try routines.forEach { r in | |
| guard r.subRoutineNames.count == r.subRoutineCompleteYn.count else { | |
| throw RoutineRepositoryError.invalidSubRoutineLength( | |
| routineId: r.routineId, | |
| names: r.subRoutineNames.count, | |
| completes: r.subRoutineCompleteYn.count | |
| ) | |
| } | |
| } | |
| let completionDTO = routines.map { | |
| RoutineCompletionDTO( | |
| routineId: $0.routineId, | |
| routineCompleteYn: $0.routineCompleteYn, | |
| subRoutineCompleteYn: $0.subRoutineCompleteYn | |
| ) | |
| } | |
| let completionListDTO = RoutineCompletionListDTO(routineCompletionInfos: completionDTO) | |
| // … | |
| } |
| func updateAllCompleted() { | ||
| allCompletedIcon.isHidden.toggle() | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
토글 기반 아이콘 표시 로직은 재바인딩/중복 갱신 시 오동작합니다 — 불리언 인자를 받도록 시그니처 변경 권장
WeekView 등에서 동일 날짜에 대해 여러 번 호출되면 isHidden.toggle()로 인해 실제 완료 상태와 표시가 뒤집힐 수 있습니다. 외부 상태를 그대로 반영하도록 변경해 주세요.
적용 diff:
- func updateAllCompleted() {
- allCompletedIcon.isHidden.toggle()
- }
+ func updateAllCompleted(isCompleted: Bool) {
+ allCompletedIcon.isHidden = !isCompleted
+ }호출부 예시(다른 파일 적용용):
// WeekView.updateAllCompletedState(allCompletedDates:)
let isCompleted = allCompletedDates.contains(date)
dateView.updateAllCompleted(isCompleted: isCompleted)다음 스크립트로 기존 호출부를 점검하고 일괄 치환 대상 라인을 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# updateAllCompleted 사용처 확인
rg -nP --type=swift '\bupdateAllCompleted\s*\(' -C2Length of output: 1567
updateAllCompleted() 토글 로직→명시적 불리언 인자 반영으로 개선 필요
토글 기반의 allCompletedIcon.isHidden.toggle()는 뷰 재사용 또는 중복 호출 시 실제 완료 상태와 표시가 뒤집혀 오동작할 수 있습니다. 외부에서 전달된 완료 여부를 그대로 반영하도록 아래와 같이 리팩터링을 진행해주세요.
• DateView.swift (127~129 줄)
- func updateAllCompleted() {
- allCompletedIcon.isHidden.toggle()
- }
+ func updateAllCompleted(isCompleted: Bool) {
+ allCompletedIcon.isHidden = !isCompleted
+ }• WeekView.swift 호출부 수정
if isAllCompleted { dateView.updateAllCompleted() }→ unconditional callif allCompletedDates.contains(dateView.key) { … }→ unconditional call
예시:
- let isAllCompleted = allCompletedDates.contains(date)
- if isAllCompleted {
- dateView.updateAllCompleted()
- }
+ let isAllCompleted = allCompletedDates.contains(date)
+ dateView.updateAllCompleted(isCompleted: isAllCompleted)
…
- for dateView in dateViews {
- if allCompletedDates.contains(dateView.key) {
- dateView.value.updateAllCompleted()
- }
- }
+ for dateView in dateViews {
+ let isAll = allCompletedDates.contains(dateView.key)
+ dateView.value.updateAllCompleted(isCompleted: isAll)
+ }이렇게 변경하면 외부 상태에 따라 항상 정확한 아이콘 표시가 보장됩니다.
📝 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.
| func updateAllCompleted() { | |
| allCompletedIcon.isHidden.toggle() | |
| } | |
| func updateAllCompleted(isCompleted: Bool) { | |
| allCompletedIcon.isHidden = !isCompleted | |
| } |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/DateView.swift around lines
127-129, change updateAllCompleted() so it accepts an explicit Bool (e.g.,
updateAllCompleted(_ isHidden: Bool)) and set allCompletedIcon.isHidden =
isHidden instead of toggling; then update the WeekView call sites to call the
method unconditionally with the desired boolean (replace conditional calls like
`if isAllCompleted { dateView.updateAllCompleted() }` and `if
allCompletedDates.contains(dateView.key) { … }` with unconditional calls passing
true/false based on the external state) so the icon always reflects the provided
completion state.
| for (index, subRoutine) in zip(routine.subRoutines, routine.subRoutineCompleted).enumerated() { | ||
| let subRoutineView = SubRoutineView(subRoutine: subRoutine, index: index) | ||
| routineContentStackView.addArrangedSubview(subRoutineView) | ||
| subRoutineView.snp.makeConstraints { make in | ||
| make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight) | ||
| } | ||
| subRoutineView.delegate = self | ||
| subRoutineViews[index] = subRoutineView | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
zip + enumerated 사용으로 타입 혼동 및 데이터 손실 위험
- 현재 루프의
subRoutine는(String, Bool)튜플입니다.SubRoutineView(subRoutine: subRoutine, ...)가 문자열을 기대한다면 컴파일 에러이며, 튜플을 기대하더라도 가독성이 떨어집니다. zip은 두 배열 길이가 다르면 긴 쪽의 잔여 요소가 무시됩니다(데이터 손실).
길이 검증 후 인덱스 기반으로 명시적으로 처리해 주세요. 서브루틴 완료 상태를 서브뷰에 초기 반영하는 것도 포함합니다.
- for (index, subRoutine) in zip(routine.subRoutines, routine.subRoutineCompleted).enumerated() {
- let subRoutineView = SubRoutineView(subRoutine: subRoutine, index: index)
+ // 방어: 길이 불일치 시 조기 반환 또는 assert
+ guard routine.subRoutines.count == routine.subRoutineCompleted.count else {
+ assertionFailure("subRoutines.count != subRoutineCompleted.count")
+ return
+ }
+ for index in 0..<routine.subRoutines.count {
+ let name = routine.subRoutines[index]
+ let isCompleted = routine.subRoutineCompleted[index]
+ let subRoutineView = SubRoutineView(subRoutine: name, index: index)
routineContentStackView.addArrangedSubview(subRoutineView)
subRoutineView.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight)
}
subRoutineView.delegate = self
subRoutineViews[index] = subRoutineView
+ // 초기 완료 상태 반영 (SubRoutineView에 API가 없다면 추가 필요)
+ subRoutineView.setCompleted?(isCompleted)
}참고: setCompleted?는 예시입니다. 실제로는 SubRoutineView에 상태 설정용 메서드(예: updateCompletedState(_:))를 제공해 주세요.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/RoutineView.swift around
lines 107–115, the current for-loop uses zip + enumerated which yields (String,
Bool) tuples and can silently drop items if the arrays differ in length; change
to an index-based loop after validating that routine.subRoutines.count ==
routine.subRoutineCompleted.count (or handle mismatches explicitly), then for
each index create SubRoutineView using the string element (not the tuple), set
its completed state immediately via a dedicated API (e.g.,
updateCompletedState(_: ) or an init parameter), apply constraints, assign
delegate, and store the view into subRoutineViews[index]; also add a
guard/assert or fallback behavior when counts mismatch to avoid data loss.
| private func configureLayout() { | ||
| addSubview(subRoutineView) | ||
| subRoutineView.addSubview(subRoutineCheckButton) | ||
| subRoutineView.addSubview(subRoutineLabel) | ||
|
|
There was a problem hiding this comment.
내부 컨테이너(subRoutineView) 제약 부족으로 레이아웃 모호 — edges 제약 추가 필요
subRoutineView를 self에 addSubview만 하고 위치/가로세로 제약을 주지 않아 오토레이아웃이 모호합니다. 현재는 높이 제약만 있어 내부 버튼/라벨 제약이 부모에게 전달되지 않습니다.
적용 diff:
- subRoutineView.snp.makeConstraints { make in
- make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight)
- }
+ subRoutineView.snp.makeConstraints { make in
+ make.edges.equalToSuperview()
+ make.height.greaterThanOrEqualTo(Layout.subRoutineViewHeight)
+ }대안: 내부 컨테이너 없이 self에 바로 subRoutineCheckButton/subRoutineLabel을 배치해도 됩니다(구조 단순화).
Also applies to: 79-81
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/SubRoutineView.swift around
lines 62-66 (and also apply same fix at lines 79-81), the subRoutineView is
added as a subview but lacks constraints tying its edges to self so Auto Layout
is ambiguous; add explicit edge constraints (top, bottom, leading, trailing or
equivalent anchors) from subRoutineView to its superview so the container has a
defined frame and can propagate constraints of its child views, or alternatively
remove the container and pin subRoutineCheckButton and subRoutineLabel directly
to self to simplify the hierarchy.
| private var dateViews: [Date: DateView] = [:] | ||
| private let calendar = Calendar.current | ||
| private var selectedDate: Date | ||
| private var allCompletedDates: [Date] = [] |
There was a problem hiding this comment.
🛠️ Refactor suggestion
날짜 비교 방식 개선 필요: Date 정확도(시간 포함) 비교로 인한 오표시 가능성
[Date]의 contains는 시각까지 동일해야 true가 됩니다. 모두완료 아이콘은 “날짜 단위” 비교가 의도이므로, calendar.startOfDay(for:)로 정규화한 후 Set<Date>로 보관하는 편이 안전하고 효율적입니다.
적용 예시:
- private var allCompletedDates: [Date] = []
+ private var allCompletedDates: Set<Date> = []정규화는 업데이트 시점에 일괄 적용하세요(아래 추가 코멘트 참조).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private var allCompletedDates: [Date] = [] | |
| private var allCompletedDates: Set<Date> = [] |
🤖 Prompt for AI Agents
Projects/Presentation/Sources/Home/View/Component/WeekView.swift around line 27:
현재 allCompletedDates가 [Date]로 시각(시간)까지 포함한 Date를 그대로 보관해 contains 비교 시 동일 시간대가
아니면 누락될 수 있으니, 모든 날짜를 calendar.startOfDay(for:)로 정규화한 후 Set<Date>로 보관하도록 변경하세요;
즉 업데이트(할당) 시점에 incoming Dates를 startOfDay로 변환해 Set으로 초기화하고, 이후 contains 체크는 이
Set을 사용해 효율적이고 날짜 단위 비교가 되도록 구현합니다.
| let isAllCompleted = allCompletedDates.contains(date) | ||
| if isAllCompleted { | ||
| dateView.updateAllCompleted() | ||
| } | ||
| dateView.didTapDateButton = { [weak self] date in |
There was a problem hiding this comment.
🛠️ Refactor suggestion
아이콘 토글 방식은 버그를 유발합니다: 토글(toggle) 대신 상태 설정(set)으로 전환 필요
DateView.updateAllCompleted()가 토글 기반이라, 동일 날짜에 여러 번 호출되면 아이콘이 숨겨졌다가 다시 표시되는 등 비결정적 상태가 됩니다. 항상 “원하는 상태”를 명시적으로 설정하는 API가 필요합니다.
아래처럼 “날짜 정규화 + 명시적 설정”으로 교체해 주세요.
- let isAllCompleted = allCompletedDates.contains(date)
- if isAllCompleted {
- dateView.updateAllCompleted()
- }
+ let isAllCompleted = allCompletedDates.contains(calendar.startOfDay(for: date))
+ dateView.setAllCompleted(isAllCompleted)추가로, DateView에 다음 메서드를 도입해 주세요(외부 파일 수정 예시):
// Projects/Presentation/Sources/Home/View/Component/DateView.swift
func setAllCompleted(_ isAllCompleted: Bool) {
allCompletedIcon.isHidden = !isAllCompleted
}가능하다면 기존 updateAllCompleted()는 내부(private)로 한정하거나 deprecate 처리하세요.
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/WeekView.swift around lines
92–96, the code calls DateView.updateAllCompleted() which is toggle-based and
causes nondeterministic UI when invoked multiple times; replace this with an
explicit state setter: normalize the date and call a new
DateView.setAllCompleted(Bool) using allCompletedDates.contains(date) so the
icon state is deterministically set. Add the new public setter to
Projects/Presentation/Sources/Home/View/Component/DateView.swift: func
setAllCompleted(_ isAllCompleted: Bool) { allCompletedIcon.isHidden =
!isAllCompleted } and make the old updateAllCompleted() private or mark it
deprecated to prevent future toggle usage.
| func updateAllCompletedState(allCompletedDates: [Date]) { | ||
| self.allCompletedDates = allCompletedDates | ||
| for dateView in dateViews { | ||
| if allCompletedDates.contains(dateView.key) { | ||
| dateView.value.updateAllCompleted() | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
상태 동기화 누락: 해제(false) 경로가 없어 아이콘이 계속 남습니다 · 성능/가독성 개선 포함
현재는 포함된 날짜에만 토글 호출을 하므로, “완료 → 미완료”로 바뀐 날짜의 아이콘이 해제되지 않습니다. 날짜별로 true/false를 모두 반영해야 합니다. 또한 SwiftLint 제안대로 for-where를 활용하면 가독성이 좋아집니다.
적용 예시:
- func updateAllCompletedState(allCompletedDates: [Date]) {
- self.allCompletedDates = allCompletedDates
- for dateView in dateViews {
- if allCompletedDates.contains(dateView.key) {
- dateView.value.updateAllCompleted()
- }
- }
- }
+ func updateAllCompletedState(allCompletedDates: [Date]) {
+ // 날짜 단위로 정규화하여 Set에 보관
+ self.allCompletedDates = Set(allCompletedDates.map { calendar.startOfDay(for: $0) })
+ // 모든 DateView에 대해 상태를 명시적으로 반영
+ for (date, view) in dateViews {
+ let isAllCompleted = self.allCompletedDates.contains(calendar.startOfDay(for: date))
+ view.setAllCompleted(isAllCompleted)
+ }
+ }추가 미세개선(선택):
- 주석의 “for-where” 경고를 반영하려면 두 번째 루프를 분기 두 개의
for-where로 나누어도 됩니다.
위 변경 후 기존 토글 API 사용처를 전수 교체했는지 확인해 주세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "🔎 토글 API 사용처 검색"
rg -n --type swift 'updateAllCompleted\s*\(' -C2 || true
echo "🔎 새 API 사용처 검색"
rg -n --type swift 'setAllCompleted\s*\(' -C2 || trueLength of output: 1723
WeekView.swift에서 기존 API 호출(updateAllCompleted) 미교체: 전수 교체가 필요합니다
현재 updateAllCompleted() 호출이 여전히 남아 있어 “완료 → 미완료” 전환 시 아이콘이 해제되지 않습니다. 또한 새로 제안된 setAllCompleted(_:) API가 코드베이스 어디에도 사용되지 않고 있습니다.
수정이 필요한 위치:
- Projects/Presentation/Sources/Home/View/Component/WeekView.swift
- 94행:
dateView.updateAllCompleted() - 112행:
dateView.value.updateAllCompleted()
- 94행:
- Projects/Presentation/Sources/Home/View/Component/DateView.swift
- 127행:
func updateAllCompleted() { … }(toggle 구현 →setAllCompleted(_:)추가 및 기존 메서드 제거)
- 127행:
제안된 변경(diff 예시):
func updateAllCompletedState(allCompletedDates: [Date]) {
- self.allCompletedDates = allCompletedDates
- for dateView in dateViews {
- if allCompletedDates.contains(dateView.key) {
- dateView.value.updateAllCompleted()
- }
- }
+ // 날짜 단위로 정규화하여 Set에 보관
+ self.allCompletedDates = Set(allCompletedDates.map { calendar.startOfDay(for: $0) })
+ // 모든 DateView에 대해 상태 명시적 반영
+ for (date, view) in dateViews where true {
+ let isCompleted = self.allCompletedDates.contains(calendar.startOfDay(for: date))
+ view.setAllCompleted(isCompleted)
+ }
} extension DateView {
- func updateAllCompleted() {
- allCompletedIcon.isHidden.toggle()
- }
+ /// 전체 완료 여부에 따라 아이콘 표시 상태를 설정합니다.
+ func setAllCompleted(_ completed: Bool) {
+ allCompletedIcon.isHidden = !completed
+ }
}수정 후, 아래 스크립트를 통해 모든 호출이 정상 교체되었는지 반드시 확인해 주세요.
#!/bin/bash
set -euo pipefail
echo "🔎 기존 API(updateAllCompleted) 호출 검색"
rg -n --type swift 'updateAllCompleted\s*\(' -C2 || echo "⚠️ 호출 없음 확인됨"
echo "🔎 새 API(setAllCompleted) 호출 검색"
rg -n --type swift 'setAllCompleted\s*\(' -C2 || echo "⚠️ 호출 없음"🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 111-111: where clauses are preferred over a single if inside a for
(for_where)
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Home/View/Component/WeekView.swift around lines
94 and 112, calls to dateView.updateAllCompleted() must be replaced so they call
setAllCompleted(_:) with an explicit Bool (true when the date is contained in
allCompletedDates, false otherwise); update the loop at 108–115 to call
dateView.value.setAllCompleted(true) for contained dates and ensure
non-contained dateViews receive setAllCompleted(false). In
Projects/Presentation/Sources/Home/View/Component/DateView.swift around line
127, remove the toggle-style func updateAllCompleted() and implement func
setAllCompleted(_ completed: Bool) that explicitly sets the completed UI state
(no toggling) and updates the icon/appearance accordingly. After making changes,
run the provided grep script to verify there are no remaining
updateAllCompleted(...) calls and that setAllCompleted(...) is present
everywhere.
🌁 Background
루틴 완료 구현했습니다 ~
📱 Screenshot
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-08-23.at.02.54.51.mp4
👩💻 Contents
📝 Review Note
죽어가는 뇌로 구현했기 때문에 .. 이상하네 ? 왜 이르케 구현했지 ..
라고 생각이 드실수도 있어요 .........
근데 일단 굴러감 ㅠㅠ
진짜 추후 빡세게 리팩해부아요 🥺
📣 Related Issue
Summary by CodeRabbit
신기능
개선
UI 변경