[Feat-T3-127] 감정 구슬 API 연동 및 추천 루틴 결과 화면 재사용#29
Conversation
- EmotionEndpoint 정의 - EmotionRepositoryProtocol, EmotionRepository 정의 및 구현 (+ EmotionResponseDTO) - EmotionUseCaseProtocol, EmotionUseCase 정의 및 구현 (+ EmotionEntity)
- 서버 통신으로 받은 값들을 보여주도록 수정 - 감정 구슬 이미지 Kingfisher로 출력
- OnboardingUseCaseProtocol, OnboardingUseCase 삭제 - OnboardingRecommendedRoutineView 삭제 - 기존 온보딩 선택지 · 감정 값을 등록하고 추천 루틴을 받아오는 로직 이동
…l 구현 (#T3-127) - 추천 루틴 결과 값을 보여주는 화면(ResultRecommendedRoutineView) 구현 - 온보딩 선택지 or 감정 구슬 선택지에 따른 추천 루틴 받아오는 로직 - 선택한 추천 루틴 등록 로직
- MypageView, OnboardingResultView, EmotionRegisterView에서 재사용할 수 있도록 분기 처리 - OnboardingResultView에 그래픽 추가
Walkthrough이번 변경에서는 감정 기반 추천 루틴 기능이 대대적으로 도입되었습니다. 도메인, 데이터소스, 프레젠테이션 계층에 감정 엔티티 및 관련 프로토콜, 레포지토리, 유스케이스, 뷰모델, 뷰가 추가 및 리팩터링되었으며, 기존 온보딩 추천 루틴 흐름이 분리·통합되었습니다. Kingfisher 외부 라이브러리가 이미지 URL 비동기 로딩을 위해 도입되었습니다. 감정 오브 이미지 자산 일부가 제거되고, 그래픽 리소스와 뷰 관련 코드가 새 구조에 맞게 정리되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant EmotionRegisterView
participant EmotionRegisterViewModel
participant EmotionUseCase
participant EmotionRepository
participant NetworkService
User->>EmotionRegisterView: 화면 진입
EmotionRegisterView->>EmotionRegisterViewModel: .fetchEmotions 액션
EmotionRegisterViewModel->>EmotionUseCase: fetchEmotions()
EmotionUseCase->>EmotionRepository: fetchEmotions()
EmotionRepository->>NetworkService: 감정 리스트 API 요청
NetworkService-->>EmotionRepository: 감정 DTO 배열 응답
EmotionRepository-->>EmotionUseCase: EmotionEntity 배열 반환
EmotionUseCase-->>EmotionRegisterViewModel: EmotionEntity 배열 반환
EmotionRegisterViewModel-->>EmotionRegisterView: Emotion 배열 퍼블리시
EmotionRegisterView->>User: 감정 리스트 표시 및 이미지 로딩 (Kingfisher)
sequenceDiagram
actor User
participant ResultRecommendedRoutineView
participant ResultRecommendedRoutineViewModel
participant ResultRecommendedRoutineUseCase
participant EmotionRepository
participant OnboardingRepository
User->>ResultRecommendedRoutineView: 감정/온보딩/마이페이지 진입
ResultRecommendedRoutineView->>ResultRecommendedRoutineViewModel: .fetchResultRecommendedRoutines 액션
alt 감정 기반
ResultRecommendedRoutineViewModel->>ResultRecommendedRoutineUseCase: fetchResultRecommendedRoutines(emotion)
ResultRecommendedRoutineUseCase->>EmotionRepository: registerEmotion(emotion)
EmotionRepository->>NetworkService: 추천 루틴 API 요청
NetworkService-->>EmotionRepository: 추천 루틴 DTO 응답
EmotionRepository-->>ResultRecommendedRoutineUseCase: RecommendedRoutineEntity 배열
ResultRecommendedRoutineUseCase-->>ResultRecommendedRoutineViewModel: RecommendedRoutineEntity 배열
else 온보딩 기반
ResultRecommendedRoutineViewModel->>ResultRecommendedRoutineUseCase: fetchResultRecommendedRoutines(onboardingChoices)
ResultRecommendedRoutineUseCase->>OnboardingRepository: 추천 루틴 API 요청
OnboardingRepository-->>ResultRecommendedRoutineUseCase: RecommendedRoutineEntity 배열
ResultRecommendedRoutineUseCase-->>ResultRecommendedRoutineViewModel: RecommendedRoutineEntity 배열
end
ResultRecommendedRoutineViewModel-->>ResultRecommendedRoutineView: 추천 루틴 퍼블리시
ResultRecommendedRoutineView->>User: 루틴 선택/등록/스킵 UI 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ 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. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 16
🔭 Outside diff range comments (1)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (1)
37-39: Dictionary 변환 로직 개선 필요현재 로직에서는 특정 타입의 choice를 찾지 못하면
break로 루프를 종료하여 불완전한 dictionary를 반환할 수 있습니다.guard let choice = onboardingChoices.first(where: { $0.onboardingType == type }) -else { break } +else { continue }또는 모든 필수 타입이 있는지 검증하는 로직을 추가하는 것도 고려해보세요.
🧹 Nitpick comments (7)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
42-54: 의존성 해결 패턴 일관성 개선 제안일부 의존성은 메서드 시작 부분에서 해결하고(authRepository, emotionRepository), 다른 의존성은 등록 클로저 내에서 해결하고 있습니다. 일관된 패턴을 사용하면 코드 가독성과 유지보수성이 향상됩니다.
모든 의존성을 메서드 시작 부분에서 해결하거나, 모두 클로저 내에서 해결하는 방식으로 통일하는 것을 고려해보세요.
Projects/DataSource/Sources/Endpoint/EmotionEndpoint.swift (1)
29-35: 불필요한 헤더 제거 제안
"accept": "*/*"헤더는 기본값이므로 명시적으로 추가할 필요가 없습니다.var headers: [String : String] { let headers: [String: String] = [ - "Content-Type": "application/json", - "accept": "*/*" + "Content-Type": "application/json" ] return headers }Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (1)
46-49: 에러 처리 구현 필요TODO 주석에 명시된 대로 사용자에게 에러를 알리는 토스트뷰 구현이 필요합니다. 과거 학습 내용에 따르면 CustomAlertView를 만들어 에러 처리를 UI로 해결하실 계획이 있으신 것으로 알고 있습니다.
CustomAlertView 구현이나 새로운 이슈 생성을 도와드릴까요?
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (1)
93-94: 불필요한 빈 배열 전송실제 데이터를 보내기 전에 빈 배열을 먼저 보내는 것은 UI 깜빡임을 유발할 수 있습니다. 빈 배열 전송을 제거하는 것이 좋습니다.
let recommendedRoutines = entities.map({ $0.toRecommendedRoutine() }) -resultRecommendedRoutinesSubject.send([]) resultRecommendedRoutinesSubject.send(recommendedRoutines)Also applies to: 101-102
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (1)
63-63: 사용하지 않는 클로저 파라미터 수정사용하지 않는 클로저 파라미터는
_로 대체하는 것이 좋습니다.-}, completion: { [weak self] finshed in +}, completion: { [weak self] _ inProjects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (2)
96-103: cancellables 초기화를 프로퍼티 선언 시점으로 이동하세요
cancellables를 프로퍼티 선언 시점에 초기화하면 초기화 로직이 더 간결해집니다.- private var cancellables: Set<AnyCancellable> + private var cancellables = Set<AnyCancellable>() private let entryPoint: EntryPoint init(entryPoint: EntryPoint, viewModel: ResultRecommendedRoutineViewModel) { self.entryPoint = entryPoint - cancellables = [] super.init(viewModel: viewModel) }
335-336: 탭 인덱스를 상수로 관리하세요하드코딩된 탭 인덱스 값을 상수로 관리하면 유지보수가 용이합니다.
+private enum TabIndex { + static let routine = 2 +} self.navigationController?.popToRootViewController(animated: false) -tabBarView.selectedIndex = 2 +tabBarView.selectedIndex = TabIndex.routine
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (24)
Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/anxiety_orb.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/anxiety_orb@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/anxiety_orb@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/calm_orb.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/calm_orb@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/calm_orb@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/lethargy_orb.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/lethargy_orb@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/lethargy_orb@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/satisfied_orb.imageset/satisfied_orb.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/satisfied_orb.imageset/satisfied_orb@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/satisfied_orb.imageset/satisfied_orb@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/tired_orb.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/tired_orb@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/tired_orb@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/vitality_orb.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/vitality_orb@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/vitality_orb@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Graphic/intro_graphic.imageset/IntroGraphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Graphic/intro_graphic.imageset/IntroGraphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Graphic/intro_graphic.imageset/IntroGraphic@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/onboarding_graphic.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/onboarding_graphic@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/onboarding_graphic@3x.pngis excluded by!**/*.png
📒 Files selected for processing (35)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift(1 hunks)Projects/DataSource/Sources/Endpoint/EmotionEndpoint.swift(1 hunks)Projects/DataSource/Sources/Repository/EmotionRepository.swift(1 hunks)Projects/Domain/Sources/DomainDependencyAssembler.swift(1 hunks)Projects/Domain/Sources/Entity/EmotionEntity.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/EmotionRepositoryProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/EmotionUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift(0 hunks)Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift(1 hunks)Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift(1 hunks)Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift(1 hunks)Projects/Presentation/Project.swift(1 hunks)Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/Contents.json(0 hunks)Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/Contents.json(0 hunks)Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/Contents.json(0 hunks)Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/Contents.json(0 hunks)Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/Contents.json(0 hunks)Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(2 hunks)Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift(1 hunks)Projects/Presentation/Sources/EmotionRegister/Model/EmotionType.swift(0 hunks)Projects/Presentation/Sources/EmotionRegister/View/Component/EmotionOrbCollectionViewCell.swift(2 hunks)Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift(4 hunks)Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/MypageView.swift(1 hunks)Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift(0 hunks)Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift(8 hunks)Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift(2 hunks)Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift(7 hunks)Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift(1 hunks)Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift(1 hunks)Tuist/Package.resolved(1 hunks)Tuist/Package.swift(1 hunks)
💤 Files with no reviewable changes (8)
- Projects/Presentation/Resources/Images.xcassets/EmotionOrb/anxiety_orb.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionOrb/tired_orb.imageset/Contents.json
- Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift
- Projects/Domain/Sources/Protocol/UseCase/OnboardingUseCaseProtocol.swift
- Projects/Presentation/Resources/Images.xcassets/EmotionOrb/vitality_orb.imageset/Contents.json
- Projects/Presentation/Sources/EmotionRegister/Model/EmotionType.swift
- Projects/Presentation/Resources/Images.xcassets/EmotionOrb/lethargy_orb.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/EmotionOrb/calm_orb.imageset/Contents.json
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (3)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (2)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift (1)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (3)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🧬 Code Graph Analysis (11)
Projects/Domain/Sources/Protocol/Repository/EmotionRepositoryProtocol.swift (2)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
fetchEmotions(15-18)Projects/DataSource/Sources/Repository/EmotionRepository.swift (2)
fetchEmotions(13-20)registerEmotion(22-29)
Projects/Domain/Sources/DomainDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
resolve(18-25)register(14-16)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
fetchEmotions(15-18)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (3)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (3)
fetchResultRecommendedRoutines(70-87)fetchResultRecommendedRoutines(90-95)fetchResultRecommendedRoutines(98-103)Projects/DataSource/Sources/Repository/OnboardingRepository.swift (1)
registerOnboarding(13-20)Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
registerEmotion(22-29)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (3)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (3)
fetchResultRecommendedRoutines(18-22)fetchResultRecommendedRoutines(24-27)registerRecommendedRoutines(29-31)Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)
toRecommendedRoutine(33-41)Projects/Shared/Sources/Logger/Logger.swift (1)
log(13-22)
Projects/Domain/Sources/Protocol/UseCase/EmotionUseCaseProtocol.swift (2)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
fetchEmotions(15-18)Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
fetchEmotions(13-20)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(14-16)resolve(18-25)
Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift (2)
Projects/Domain/Sources/UseCase/ResultRecommendedRoutine/ResultRecommendedRoutineUseCase.swift (3)
fetchResultRecommendedRoutines(18-22)fetchResultRecommendedRoutines(24-27)registerRecommendedRoutines(29-31)Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (3)
fetchResultRecommendedRoutines(70-87)fetchResultRecommendedRoutines(90-95)fetchResultRecommendedRoutines(98-103)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
register(14-16)
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (1)
fetchEmotions(13-20)
Projects/DataSource/Sources/Endpoint/EmotionEndpoint.swift (1)
Projects/DataSource/Sources/Repository/EmotionRepository.swift (2)
fetchEmotions(13-20)registerEmotion(22-29)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift
[Warning] 63-63: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift
[Warning] 47-47: TODOs should be resolved (에러 처리 토스트뷰)
(todo)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (29)
Projects/Presentation/Project.swift (1)
16-16: LGTM! Kingfisher 의존성 추가가 적절합니다.감정 구슬 이미지를 URL에서 비동기적으로 로드하기 위한 Kingfisher 라이브러리 추가가 PR 목표와 일치하며, 의존성 배열에서의 위치도 적절합니다.
Tuist/Package.resolved (1)
21-29: LGTM! Kingfisher 패키지 해상도가 올바르게 추가되었습니다.Kingfisher 8.5.0 버전이 적절하게 해상도에 추가되었으며, 패키지 구조와 리비전 정보가 올바릅니다.
Tuist/Package.swift (1)
9-9: LGTM! Kingfisher 패키지 의존성이 적절하게 선언되었습니다.Kingfisher 8.0.0 이상 버전으로 최소 버전 제약이 적절하게 설정되었으며, 패키지 URL이 올바릅니다.
Projects/Domain/Sources/Protocol/Repository/EmotionRepositoryProtocol.swift (1)
8-18: LGTM! 잘 설계된 Repository 프로토콜입니다.EmotionRepositoryProtocol이 Clean Architecture 원칙을 잘 따르고 있습니다:
- 비동기 네트워크 작업에 적합한
async throws메서드 시그니처- DTO가 아닌 도메인 엔티티 반환으로 계층 분리 준수
- 명확하고 목적이 분명한 메서드 네이밍
- 포괄적인 한국어 문서화
관련 코드에서 DataSource 계층의 구현체와 Domain 계층의 UseCase에서 적절히 활용되는 것을 확인했습니다.
Projects/Presentation/Resources/Images.xcassets/Graphic/onboarding_graphic.imageset/Contents.json (1)
4-4: LGTM! 이미지 에셋 이름이 일관되게 변경되었습니다.모든 스케일 팩터(1x, 2x, 3x)에서 "satisfied_orb"에서 "onboarding_graphic"으로 파일명이 일관되게 변경되었습니다. 이는 정적 감정 구슬 이미지에서 API 기반 동적 데이터로의 리팩터링 목표와 잘 맞습니다.
Also applies to: 9-9, 14-14
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
32-34: 의존성 등록이 올바르게 구현되었습니다.EmotionRepositoryProtocol의 등록이 기존 패턴과 일치하며, 감정 기능을 위한 의존성 주입이 적절히 설정되었습니다.
Projects/Domain/Sources/Protocol/UseCase/EmotionUseCaseProtocol.swift (1)
8-12: 프로토콜 정의가 명확하고 적절합니다.비동기 처리와 에러 핸들링을 위한 async throws 시그니처가 올바르게 정의되었고, 한국어 문서화도 잘 작성되었습니다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (1)
161-166: MyPage에서 목표 재설정 플로우가 적절히 구현되었습니다.
isFromMypage파라미터를 통해 온보딩 플로우를 구분하고,hidesBottomBarWhenPushed를 설정하여 네비게이션 UX를 개선한 것이 좋습니다.Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (3)
32-34: OnboardingViewModel 등록이 단순화되었습니다.의존성이 제거되어 등록이 간소화된 것이 적절합니다.
50-55: EmotionRegisterViewModel의 의존성 주입이 올바르게 구현되었습니다.EmotionUseCaseProtocol 의존성을 적절히 해결하고, 누락 시 명확한 에러 메시지를 제공합니다.
61-66: ResultRecommendedRoutineViewModel 등록이 적절히 추가되었습니다.새로운 뷰모델의 의존성 주입이 기존 패턴과 일치하며, 적절한 에러 처리를 포함합니다.
Projects/Domain/Sources/UseCase/Emotion/EmotionUseCase.swift (1)
8-19: EmotionUseCase 구현이 클린 아키텍처 원칙을 잘 따릅니다.의존성 주입, 에러 전파, 그리고 레포지토리 계층으로의 단순한 패스스루 구현이 적절합니다. 비즈니스 로직이 필요하지 않은 현재 요구사항에 맞는 깔끔한 구현입니다.
Projects/Presentation/Sources/EmotionRegister/View/Component/EmotionOrbCollectionViewCell.swift (2)
8-8: Kingfisher 라이브러리 추가가 적절합니다.비동기 이미지 로딩을 위한 Kingfisher 라이브러리 도입이 정적 이미지에서 동적 URL 기반 이미지 로딩으로의 전환에 적합합니다.
57-63: API 기반 데이터 모델로의 전환이 잘 구현되었습니다.기존
EmotionTypeenum에서Emotion모델로 변경되어 동적 데이터를 처리할 수 있게 되었고, optional URL에 대한 안전한 처리와 Kingfisher를 통한 이미지 로딩이 적절히 구현되었습니다.Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift (2)
39-50: MyPage 진입점 구분을 위한 플래그 추가가 적절합니다.
isFromMypage플래그를 통해 온보딩과 MyPage에서의 목표 재설정 플로우를 구분할 수 있게 되었습니다. 기본값false설정으로 기존 동작을 보장하면서 새로운 요구사항을 지원합니다.
221-227: 네비게이션 체인에서 상태 전달이 올바르게 구현되었습니다.다음 단계로 이동할 때
isFromMypage플래그를 일관되게 전달하여 전체 플로우에서 진입점 정보를 보존합니다. 이는 추천 루틴 결과 화면 재사용이라는 PR 목표와 일치합니다.Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift (1)
11-15: 프레젠테이션 모델 구조가 적절합니다.도메인과 프레젠테이션 계층 간의 명확한 분리를 위한 별도 모델 정의가 잘 되어 있습니다.
Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift (1)
15-17: 정적 이미지 자산 정리가 적절합니다.기존 정적 감정 구슬 이미지들을 제거하고 API 기반 동적 이미지 로딩으로 전환한 것이 전체 아키텍처 변경사항과 일치합니다. 새로운
onboardingGraphic추가와 이미지 명명 규칙 개선도 좋습니다.Projects/Domain/Sources/Protocol/UseCase/ResultRecommendedRoutineUseCaseProtocol.swift (1)
10-24: 프로토콜 설계가 깔끔합니다각 메서드가 명확한 책임을 가지고 있으며, 문서화도 잘 되어 있습니다. 온보딩과 감정 기반 추천 루틴 조회를 별도 메서드로 분리한 설계가 적절합니다.
Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (1)
114-117: 컬렉션 뷰 리로드 최적화감정 리스트가 비어있지 않을 때만 리로드하는 것은 좋은 접근입니다. 성능 최적화가 잘 되어있네요.
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (1)
197-209: 의존성 주입 및 뷰 구성 로직 잘 구현됨
isFromMypage플래그에 따라 적절히 분기 처리하여 뷰모델을 구성하고 뷰를 생성하는 로직이 깔끔하게 구현되었습니다.Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (4)
13-28: Input/Output 구조가 단순화되어 개선되었습니다!추천 루틴 관련 로직이 제거되고 온보딩 선택에만 집중하도록 리팩토링된 것이 좋습니다.
30-49: 의존성이 제거되어 뷰모델이 더 단순해졌습니다!온보딩 로직에만 집중하도록 구조가 개선되었습니다.
142-146: 다음 버튼 활성화 로직이 단순화되어 좋습니다!감정 선택의 경우 비어있지 않으면 활성화되는 간단한 로직이 적절합니다.
167-184: 온보딩 선택지 통합 로직이 안전하게 구현되었습니다!필수 선택 항목 검증과 배열 구성이 적절합니다.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (4)
15-65: EntryPoint enum 설계가 잘 되어 있습니다!진입점별로 UI 동작과 텍스트를 명확하게 분리하여 관리하는 구조가 좋습니다. 특히 mypage에서는 루틴 선택을 비활성화하고 건너뛰기 버튼을 숨기는 등 각 플로우에 맞는 세심한 처리가 잘 되어 있습니다.
67-87: 레이아웃 상수 관리가 훌륭합니다!화면 크기에 따른 동적 여백 조정이 잘 구현되어 있어 다양한 기기에서 일관된 사용자 경험을 제공할 수 있습니다.
109-118: 뷰 라이프사이클 메서드가 적절히 구현되었습니다!추천 루틴 fetch 타이밍과 네비게이션 설정이 적절한 시점에 수행됩니다.
119-213: UI 구성이 체계적으로 잘 되어 있습니다!진입점별 UI 속성 설정과 레이아웃 제약이 명확하게 구현되어 있습니다. 특히 진입점에 따른 버튼 상태 초기화가 각 플로우의 요구사항에 맞게 적절히 처리되었습니다.
| return EmotionEntity( | ||
| emtionType: type, | ||
| emotionName: name, | ||
| emotionImageUrl: URL(string: imageUrl)) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
URL 생성 시 안전성 확보 필요
URL(string:)은 유효하지 않은 URL 문자열의 경우 nil을 반환할 수 있습니다. API 응답의 imageUrl이 항상 유효한 URL 형식임을 보장할 수 없다면, 안전한 처리가 필요합니다.
EmotionEntity의 emotionImageUrl이 옵셔널 타입이라면 현재 코드가 적절합니다. 그렇지 않다면 기본값 처리나 에러 처리를 고려해주세요.
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift at line 28, the
URL(string: imageUrl) initializer can return nil if imageUrl is invalid. Check
if emotionImageUrl in EmotionEntity is optional; if not, modify the code to
safely unwrap the URL or provide a default URL or error handling to avoid
runtime crashes due to nil values.
There was a problem hiding this comment.
맞는 말씀이지만, 서버님들이 유효한 URL을 내려주실거라 믿어요~
어차피 유효한 URL이 아니면 기본 이미지를 표시하겟죠???????
There was a problem hiding this comment.
마주아요 !!! 유효한 URL을 내려주실거지만 ~ 없다면 default 구슬 (empty 구슬을 보여드리겠슴니다 !!!)
There was a problem hiding this comment.
@taipaise 네, 말씀하신 대로 EmotionEntity의 emotionImageUrl이 옵셔널 타입(URL?)이라서 현재 구현이 안전합니다!
그리고 Kingfisher를 사용하고 계시니 nil이나 유효하지 않은 URL의 경우 자동으로 placeholder나 기본 이미지를 표시해줄 거예요. 서버 팀을 믿고 가시는 것도 합리적인 접근 방식이네요 👍
/| /|
( :v: )
|(_)|
/ \
/ \
/_________\
✏️ Learnings added
Learnt from: taipaise
PR: YAPP-Github/Bitnagil-iOS#29
File: Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift:28-28
Timestamp: 2025-07-30T13:04:33.867Z
Learning: 사용자 taipaise는 서버에서 제공하는 데이터의 품질을 신뢰하는 편이며, URL 검증보다는 서버 팀의 데이터 검증을 믿는 접근 방식을 선호한다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| let endpoint = EmotionEndpoint.fetchEmotions | ||
| guard let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self) | ||
| else { return [] } | ||
|
|
||
| let emotionEntities = response.compactMap({ $0.toEmotionEntity() }) | ||
| return emotionEntities | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
에러 처리 개선 필요
두 메서드 모두 네트워크 요청 실패 시 빈 배열을 반환하고 있습니다. 이는 실제로 빈 결과와 에러를 구분하기 어렵게 만듭니다.
호출하는 측에서 에러를 적절히 처리할 수 있도록 에러를 throw하는 것이 좋습니다:
-guard let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)
-else { return [] }
+let response = try await networkService.request(endpoint: endpoint, type: [EmotionResponseDTO].self)Also applies to: 22-29
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/EmotionRepository.swift around lines
14 to 20 and 22 to 29, the current implementation returns an empty array on
network request failure, which makes it impossible to distinguish between an
actual empty result and an error. Modify both methods to throw errors instead of
returning empty arrays when the network request fails. Update the function
signatures to include throws and propagate the caught errors so that callers can
handle them appropriately.
| case .emotion: | ||
| if let navigationController = self.navigationController { | ||
| let viewControllers = navigationController.viewControllers | ||
| if viewControllers.count >= 3 { | ||
| navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false) | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
네비게이션 로직을 개선하세요
현재 하드코딩된 값(3)으로 네비게이션 스택을 관리하는 것은 취약합니다. 특정 뷰 타입을 찾아서 이동하는 방식이 더 안전합니다.
case .emotion:
if let navigationController = self.navigationController {
let viewControllers = navigationController.viewControllers
- if viewControllers.count >= 3 {
- navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false)
- }
+ // EmotionRegisterView로 돌아가기
+ if let targetVC = viewControllers.first(where: { $0 is EmotionRegisterView }) {
+ navigationController.popToViewController(targetVC, animated: 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.
| case .emotion: | |
| if let navigationController = self.navigationController { | |
| let viewControllers = navigationController.viewControllers | |
| if viewControllers.count >= 3 { | |
| navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false) | |
| } | |
| } | |
| case .emotion: | |
| if let navigationController = self.navigationController { | |
| let viewControllers = navigationController.viewControllers | |
| // EmotionRegisterView로 돌아가기 | |
| if let targetVC = viewControllers.first(where: { $0 is EmotionRegisterView }) { | |
| navigationController.popToViewController(targetVC, animated: false) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift
around lines 310 to 316, the navigation logic uses a hardcoded index (3) to pop
to a view controller, which is fragile. Instead, iterate through the
navigationController's viewControllers array to find the specific view
controller type you want to navigate back to, then pop to that view controller
safely without relying on fixed indices.
| guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) | ||
| else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
의존성 주입 실패를 더 우아하게 처리하세요
fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.
-guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self)
-else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") }
+guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else {
+ BitnagilLogger.log(logType: .error, message: "routineCreationViewModel 의존성이 등록되지 않았습니다.")
+ return
+}🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift
around lines 339 to 340, replace the fatalError call used when dependency
injection for RoutineCreationViewModel fails with optional binding that
gracefully handles the nil case. Add error logging to record the failure instead
of crashing the app, allowing the app to continue running or handle the error
appropriately.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (4)
Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (1)
126-127: 의존성 해결 실패 시 더 안전한 처리가 필요합니다의존성이 등록되지 않은 경우 앱이 크래시됩니다. 사용자에게 더 나은 경험을 제공하기 위해 에러 처리를 고려해보세요.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (3)
237-248: 루틴 등록 실패 시 사용자 피드백을 추가하세요현재 등록 실패 시 로그만 남기고 있습니다. 사용자에게 에러 알림을 표시하는 것이 좋겠습니다.
학습 기록에 따르면 CustomAlertView를 만들어 에러 처리를 할 계획이 있으신 것으로 알고 있습니다. 에러 알림 구현을 도와드릴까요?
310-316: 네비게이션 로직을 개선하세요현재 하드코딩된 값(3)으로 네비게이션 스택을 관리하는 것은 취약합니다. 특정 뷰 타입을 찾아서 이동하는 방식이 더 안전합니다.
339-340: 의존성 주입 실패를 더 우아하게 처리하세요fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.
🧹 Nitpick comments (1)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (1)
94-95: UI 깜빡임을 방지하기 위해 이중 send 패턴을 개선해보세요빈 배열을 먼저 보낸 후 실제 데이터를 보내는 패턴이 UI에서 깜빡임을 유발할 수 있습니다.
다음과 같이 개선할 수 있습니다:
- resultRecommendedRoutinesSubject.send([]) - resultRecommendedRoutinesSubject.send(recommendedRoutines) + resultRecommendedRoutinesSubject.send(recommendedRoutines)Also applies to: 102-103
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift(1 hunks)Projects/Domain/Sources/DomainDependencyAssembler.swift(1 hunks)Projects/Domain/Sources/Entity/EmotionEntity.swift(1 hunks)Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift(1 hunks)Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift(4 hunks)Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift(1 hunks)Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift(1 hunks)Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- Projects/Domain/Sources/DomainDependencyAssembler.swift
- Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift
- Projects/Domain/Sources/Entity/EmotionEntity.swift
- Projects/DataSource/Sources/DTO/EmotionResponseDTO.swift
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (2)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (5)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (3)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
🧬 Code Graph Analysis (1)
Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (2)
Projects/Presentation/Sources/EmotionRegister/Model/Emotion.swift (1)
toEmotion(18-23)Projects/Shared/Sources/Logger/Logger.swift (1)
log(13-22)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift
[Warning] 47-47: TODOs should be resolved (에러 처리 토스트뷰)
(todo)
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift
[Warning] 84-84: TODOs should be resolved (에러 처리)
(todo)
🔇 Additional comments (6)
Projects/Presentation/Sources/EmotionRegister/View/EmotionRegisterView.swift (1)
41-41: 동적 감정 데이터 구조로의 개선 좋습니다!정적 enum에서 API 기반 동적 데이터로 변경한 아키텍처 개선이 우수합니다. 확장성과 유지보수성이 크게 향상되었습니다.
Projects/Presentation/Sources/EmotionRegister/ViewModel/EmotionRegisterViewModel.swift (2)
24-30: 도메인 계층 통합이 잘 구현되었습니다!UseCase 의존성 주입과 Combine 퍼블리셔 구조가 깔끔하게 설계되었습니다. 관심사 분리가 잘 이루어졌네요.
39-51: 비동기 에러 처리 구조가 적절합니다async/await 패턴과 에러 로깅이 잘 구현되어 있습니다. 학습 기록에 따르면 CustomAlertView 구현 계획이 있으시니, 현재 TODO 주석은 적절합니다.
Projects/Presentation/Sources/ResultRecommendedRoutine/ViewModel/ResultRecommendedRoutineViewModel.swift (2)
14-18: 다양한 컨텍스트를 지원하는 깔끔한 아키텍처입니다!ViewModelType enum을 통해 온보딩, 마이페이지, 감정 등록의 세 가지 흐름을 하나의 ViewModel로 통합한 설계가 우수합니다.
116-119: 감정 컨텍스트의 단일 선택 로직이 잘 구현되었습니다감정 기반 루틴 추천에서는 단일 선택만 허용하는 비즈니스 로직이 적절히 구현되어 있습니다.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (1)
15-65: EntryPoint enum 설계가 매우 우수합니다!세 가지 진입점(온보딩, 마이페이지, 감정 등록)에 따른 UI 차이를 computed property로 깔끔하게 처리한 설계가 훌륭합니다. 코드 중복 없이 각 컨텍스트의 요구사항을 잘 반영했습니다.
| return EmotionEntity( | ||
| emtionType: type, | ||
| emotionName: name, | ||
| emotionImageUrl: URL(string: imageUrl)) |
There was a problem hiding this comment.
맞는 말씀이지만, 서버님들이 유효한 URL을 내려주실거라 믿어요~
어차피 유효한 URL이 아니면 기본 이미지를 표시하겟죠???????
| static let skipButtonBottomSpacing: CGFloat = 20 | ||
|
|
||
| static var mainLabelTopSpacing: CGFloat { | ||
| let height = UIScreen.main.bounds.height |
There was a problem hiding this comment.
정말 슬프게도,, UIScreen.main은 deprecated 되었답니다 ㅜㅜ

아래와 같은 방법으로도, 화면의 height를 구할 수 있긴 합니다
guard let screen = view.window?.windowScene?.screen else { return }
let height = screen.bounds.height이미 알고 계신 부분일 수 있지만, 사실 위와 같은 코드는 지금같은 상황에서는 view 인스턴스가 없어서 높이를 잡을 수 없는 문제가 발생할 것 같긴합니다.. 따라서 Layout 열거형 안에서 디바이스 화면 크기를 바탕으로 값을 유동적으로 가져가기 조금 힘들지 않을까?? 합니다.
혹시 이렇게 디바이스에 따라 값을 다르게 한 이유가 있을까요? 추가로 만약 꼭 디바이스 높이에 따라 다르게 해야한다면, 이 부분은 Layout 열거형 안이 아닌, VC의 프로퍼티로 관리해야할 수도 있을 것 같습니다~!
| confirmButton.snp.makeConstraints { make in | ||
| make.leading.equalTo(safeArea).offset(Layout.horizontalMargin) | ||
| make.trailing.equalTo(safeArea).inset(Layout.horizontalMargin) | ||
| if entryPoint.isHiddenSkipButton { |
There was a problem hiding this comment.
분기가 많이 들어가긴 하네요 ㅜㅜ 고생하셨습니다.
| } | ||
|
|
||
| // ViewModelType의 설정합니다. (setter) | ||
| func configure(viewModelType: ResultRecommendedRoutineViewModelType) { |
There was a problem hiding this comment.
진짜 꼭!!! 나중에 고도화 해봐요!!!! setter 주입 좀 더 고민해보는걸로~!
There was a problem hiding this comment.
인정인정 데스노트 다 죽이기 목표 .......... ☠️🔥
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (1)
213-214: 의존성 주입 실패를 더 우아하게 처리하세요fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.
- guard let resultRecommendedRoutineViewModel = DIContainer.shared.resolve(type: ResultRecommendedRoutineViewModel.self) - else{ fatalError("resultRecommendedRoutineViewModel 의존성이 등록되지 않았습니다.") } + guard let resultRecommendedRoutineViewModel = DIContainer.shared.resolve(type: ResultRecommendedRoutineViewModel.self) else { + BitnagilLogger.log(logType: .error, message: "resultRecommendedRoutineViewModel 의존성이 등록되지 않았습니다.") + return + }Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (3)
253-264: 루틴 등록 실패 시 사용자 피드백을 추가하세요현재 등록 실패 시 로그만 남기고 있습니다. 사용자에게 에러 알림을 표시하는 것이 좋겠습니다.
learnings에 따르면 CustomAlertView를 만들어 에러 처리를 할 계획이 있으신 것으로 알고 있습니다. 에러 알림 구현을 도와드릴까요?
326-332: 네비게이션 로직을 개선하세요현재 하드코딩된 값(3)으로 네비게이션 스택을 관리하는 것은 취약합니다. 특정 뷰 타입을 찾아서 이동하는 방식이 더 안전합니다.
case .emotion: if let navigationController = self.navigationController { let viewControllers = navigationController.viewControllers - if viewControllers.count >= 3 { - navigationController.popToViewController(viewControllers[viewControllers.count - 3], animated: false) - } + // EmotionRegisterView로 돌아가기 + if let targetVC = viewControllers.first(where: { $0 is EmotionRegisterView }) { + navigationController.popToViewController(targetVC, animated: false) + } }
355-356: 의존성 주입 실패를 더 우아하게 처리하세요fatalError 대신 옵셔널 바인딩으로 처리하고 에러 로깅을 추가하는 것이 좋습니다.
- guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) - else { fatalError("routineCreationViewModel 의존성이 등록되지 않았습니다.") } + guard let routineCreationViewModel = DIContainer.shared.resolve(type: RoutineCreationViewModel.self) else { + BitnagilLogger.log(logType: .error, message: "routineCreationViewModel 의존성이 등록되지 않았습니다.") + return + }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift(9 hunks)Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift(5 hunks)Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Projects/Presentation/Sources/Onboarding/View/OnboardingView.swift
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (5)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (3)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: #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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🧬 Code Graph Analysis (1)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (2)
Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)
action(51-65)Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
resolve(18-25)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift
[Warning] 63-63: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (10)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift (7)
10-11: 적절한 모듈 임포트입니다
Shared모듈은 DIContainer 사용을 위해,SnapKit은 레이아웃 제약 조건 설정을 위해 필요합니다.
18-27: 레이아웃 상수 정의가 개선되었습니다화면 크기에 따른 동적 spacing 처리와 그래픽 뷰의 고정 크기 설정이 적절합니다.
36-47: 프로퍼티 정의와 초기화가 적절합니다
UIImageView로의 변경, 레이아웃 업데이트 추적, 그리고 네비게이션 컨텍스트를 위한isFromMypage플래그 추가가 모두 적절합니다.
75-89: 레이아웃 업데이트 로직이 잘 구현되었습니다
viewDidLayoutSubviews에서 화면 크기에 따른 동적 spacing 조정이 적절하게 구현되었으며, 중복 업데이트 방지 로직도 포함되어 있습니다.
109-109: 그래픽 뷰 설정이 개선되었습니다
UIImageView에 맞게 이미지 설정이 적절하게 변경되었습니다.
127-127: 레이아웃 제약 조건이 개선되었습니다mainLabel의 top constraint 참조 저장과 graphicView의 중앙 정렬 및 고정 크기 설정이 적절합니다.
Also applies to: 152-154
166-171: 적절한 바인딩 구현입니다Combine을 사용한 reactive 프로그래밍과 메모리 관리가 적절하게 구현되었습니다.
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (3)
16-66: EntryPoint 열거형이 잘 설계되었습니다각 진입점에 대한 UI 구성을 computed property로 깔끔하게 관리하고 있습니다. 유지보수성과 확장성이 뛰어난 설계입니다.
68-84: 레이아웃 상수가 잘 정의되었습니다모든 레이아웃 값이 명확하게 정의되어 있으며, 화면 크기에 따른 동적 spacing 처리를 위한 상수들이 적절히 포함되어 있습니다.
128-133: 화면 크기에 따른 동적 spacing 처리가 적절합니다
view.bounds.height를 사용하여 deprecated API를 피하고, SE 기기 대응을 위한 동적 spacing 조정이 잘 구현되었습니다.
| UIView.animate(withDuration: 0.5, delay: 2, options: .curveEaseInOut, animations: { | ||
| self.view.alpha = 0.0 | ||
| }, completion: { [weak self] finished in | ||
| guard let self else { return } | ||
| let recommendedRoutineView = OnboardingRecommendedRoutineView(viewModel: self.viewModel) | ||
| self.navigationController?.pushViewController(recommendedRoutineView, animated: true) | ||
| }, completion: { [weak self] finshed in | ||
| self?.viewModel.action(input: .fetchOnboardingChoices) | ||
| }) |
There was a problem hiding this comment.
사용하지 않는 파라미터를 수정하세요
SwiftLint가 지적한 대로 사용하지 않는 클로저 파라미터는 _로 대체해야 합니다.
- }, completion: { [weak self] finshed in
+ }, completion: { [weak self] _ in
self?.viewModel.action(input: .fetchOnboardingChoices)📝 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.
| UIView.animate(withDuration: 0.5, delay: 2, options: .curveEaseInOut, animations: { | |
| self.view.alpha = 0.0 | |
| }, completion: { [weak self] finished in | |
| guard let self else { return } | |
| let recommendedRoutineView = OnboardingRecommendedRoutineView(viewModel: self.viewModel) | |
| self.navigationController?.pushViewController(recommendedRoutineView, animated: true) | |
| }, completion: { [weak self] finshed in | |
| self?.viewModel.action(input: .fetchOnboardingChoices) | |
| }) | |
| UIView.animate(withDuration: 0.5, delay: 2, options: .curveEaseInOut, animations: { | |
| self.view.alpha = 0.0 | |
| }, completion: { [weak self] _ in | |
| self?.viewModel.action(input: .fetchOnboardingChoices) | |
| }) |
🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 63-63: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Onboarding/View/OnboardingResultView.swift
around lines 61 to 65, the closure parameter 'finshed' in the completion block
is unused. Replace the parameter name with an underscore (_) to indicate it is
intentionally unused and satisfy SwiftLint rules.


🌁 Background
감정 구슬 목록을 불러오는 API를 연동하고, 추천 루틴 결과 화면을 재사용할 수 있도록 수정했습니다.
📱 Screenshot
1. 목표 재설정에서의 추천 루틴 결과 화면
2. 온보딩에서의 추천 루틴 결과 화면
3. 감정 등록에서의 추천 루틴 결과 화면
4. 그래픽 추가된 온보딩 결과 화면
전체 흐름 (온보딩)
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-07-29.at.23.42.22.mp4
전체 흐름 (목표 재설정)
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-07-29.at.23.39.48.mp4
전체 흐름 (감정 등록)
Simulator.Screen.Recording.-.iPhone.13.mini.-.2025-07-30.at.00.02.56.mp4
감정 등록은 1일 1등록이라 테스트가 어려워요 ㅠ ㅠ ... 테스트 하긴 했어요 ㅠㅠ 🔥
👩💻 Contents
✅ Testing
1. 온보딩 > 추천 루틴 Flow
2. 마이페이지 > 추천 루틴 Flow
3. 감정등록 > 추천 루틴 Flow
📝 Review Note
3가지 화면이 매우매우 동일해보이지만, 꽤 소소하게 다른 포인트를 갖고 있습니다.
온보딩 · 목표 재설정은 OnboardingView를 같이 사용하고 있어서,
isFromMypage라는 값을 받아 분기처리 하도록 했습니다.그리고 ResultRecommendedRoutineViewModel에서는 viewModelType을 받아서 분기처리 할 수 있도록 했습니다. (띵 감사 !!!😇!)
다만, View와는 약간 다르다고 생각해서 ResultRecommendedRoutineView에서는 따로 EntryPoint를 받아서 위의 표들에 대한 분기처리를 하도록 했습니다.
생각보다 분기처리할 것이 많아 View에서 값을 갖고 있는게 좋다고 생각했어요 ㅠㅠ. ...
추천 루틴 결과 화면(
ResultRecommendedRoutineView)에서 다음 화면으로 이동하는게 각각 다른데 ...특히 감정 등록 화면에서는 또 홈에서 왔는지 감정 등록 화면에서 왔는지 분기 처리할 값을 넣기가 애매해서 ... 일단 popToViewController으로 처리했습니다. ....
그리고 마이페이지로 돌아가는 것도.. popToRootViewController로 처리했어요 ...
유저 입장에서 넘 어색해보이지 않을까요 ㅠㅠ ?? 더 좋은 방안이 있을까요 ??
📣 Related Issue
Summary by CodeRabbit
Summary by CodeRabbit
새로운 기능
버그 수정 및 개선
라이브러리 및 의존성
기타