[Feat-T3-70] 추천 루틴 UI 구현#21
Conversation
- plus, chevron 아이콘 이미지 asset 추가 - BitnagilIcon plus, chevron 아이콘 케이스 추가 - UIImage extension을 통해 resize 및 roate 함수 구현
- RoutineLevelType enum 정의 - RoutineLevelView UI 구현 (+ RoutineLevelCell) - RoutineLevelButton 구현
- 기존 테스트용 View 삭제
|
""" Walkthrough이번 변경에서는 추천 루틴(RecommendedRoutine) 관련 신규 UI 컴포넌트, 뷰, 뷰모델, 모델(enum 등), 이미지 에셋 및 유틸리티 확장(extension)들이 대거 추가되었습니다. 또한, DI 컨테이너 등록, 탭바 뷰의 추천 탭 교체, 커스텀 바텀시트 구현 및 뷰컨트롤러 확장 등도 포함되어 있습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant TabBarView
participant DIContainer
participant RecommendedRoutineView
participant RecommendedRoutineViewModel
participant RoutineLevelView
User->>TabBarView: 추천 탭 선택
TabBarView->>DIContainer: RecommendedRoutineViewModel resolve
TabBarView->>RecommendedRoutineView: 생성 및 표시
RecommendedRoutineView->>RecommendedRoutineViewModel: fetchRecommendedRoutines(category)
RecommendedRoutineViewModel-->>RecommendedRoutineView: recommendedRoutinePublisher
User->>RecommendedRoutineView: 카테고리/난이도/카드 선택
RecommendedRoutineView->>RecommendedRoutineViewModel: action(input)
RecommendedRoutineViewModel-->>RecommendedRoutineView: 상태 변경(Publisher)
User->>RecommendedRoutineView: 난이도 버튼 탭
RecommendedRoutineView->>RoutineLevelView: 바텀시트로 표시
RoutineLevelView-->>RecommendedRoutineView: 난이도 선택 델리게이트 콜백
RecommendedRoutineView->>RecommendedRoutineViewModel: action(selectLevel)
Poem
""" 📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ 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: 4
🔭 Outside diff range comments (1)
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (1)
29-36: extension 메서드에서 새로운 속성을 처리하지 않는 문제가 있습니다.
toRecommendedRoutine()메서드가 새로 추가된routineCategory속성을 설정하지 않아서, 이 메서드로 생성된 인스턴스는 기본값도 갖지 않게 됩니다.extension RecommendedRoutineEntity { func toRecommendedRoutine() -> RecommendedRoutine { return RecommendedRoutine( id: id, mainTitle: title, - subTitle: description) + subTitle: description, + routineCategory: .recommendation) } }
🧹 Nitpick comments (10)
Projects/Presentation/Sources/Common/Extension/UIImage+.swift (1)
10-72: 이미지 변환 유틸리티가 잘 구현되어 있지만 에지 케이스 처리를 개선해주세요.
UIGraphicsImageRenderer사용으로 성능이 좋고, 각 메서드의 목적이 명확합니다. 하지만 몇 가지 개선점이 있습니다:
- division by zero 방지 필요:
resizeAspectFit와resizeAspectFill에서 높이가 0일 때 크래시 가능성- 입력 검증 추가 권장: 음수나 0 크기 입력에 대한 처리
- 메모리 최적화: 큰 이미지 처리 시 메모리 사용량 고려
다음과 같은 개선을 고려해보세요:
func resizeAspectFit(to size: CGSize) -> UIImage? { + guard self.size.width > 0, self.size.height > 0, size.width > 0, size.height > 0 else { return nil } let aspectRatio = self.size.width / self.size.height let targetRatio = size.width / size.height // ... 기존 코드 }Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)
79-82: 바텀시트 프레젠테이션 API가 깔끔하게 구현되어 있습니다.
CustomBottomSheet프레젠테이션을 위한 편리한 API를 제공하고 있으며, 메서드 시그니처와 구현이 적절합니다. 향후 필요에 따라 completion handler나 애니메이션 옵션 추가를 고려할 수 있겠습니다.추가 개선사항을 고려해보세요:
- func presentCustomBottomSheet(contentViewController: UIViewController, maxHeight: CGFloat) { + func presentCustomBottomSheet(contentViewController: UIViewController, maxHeight: CGFloat, completion: (() -> Void)? = nil) { let bottomSheet = CustomBottomSheet(contentViewController: contentViewController, maxHeight: maxHeight) - present(bottomSheet, animated: true) + present(bottomSheet, animated: true, completion: completion) }Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift (1)
23-27: 옵셔널 변수 초기화 개선 필요옵셔널 변수를
nil로 초기화하는 것은 불필요합니다. Swift에서 옵셔널 변수는 기본적으로nil로 초기화됩니다.다음과 같이 수정하세요:
- private var level: RoutineLevelType? = nil { + private var level: RoutineLevelType? {Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift (1)
46-54: 중복된 정렬 로직 최적화 제안
RoutineCategoryType.allCases.sorted(by: { $0.id < $1.id })가configureAttribute와configureLayout에서 두 번 호출되고 있습니다. 성능 최적화를 위해 정렬된 결과를 프로퍼티로 캐시하는 것을 고려해보세요.final class RoutineCategoryView: UIView { + private let sortedCategories = RoutineCategoryType.allCases.sorted(by: { $0.id < $1.id }) private func configureAttribute() { // ... existing code ... - RoutineCategoryType.allCases.sorted(by: { $0.id < $1.id }).forEach { type in + sortedCategories.forEach { type in // ... existing code ... } } }Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (2)
14-14: 불필요한 public 접근 제한자내부 컴포넌트에
public접근 제한자가 필요하지 않을 것 같습니다.internal(기본값)로 충분할 것 같습니다.- public final class RoutineLevelView: UIViewController { + final class RoutineLevelView: UIViewController {
59-59: 정렬 로직 최적화 제안다른 파일과 마찬가지로 정렬된 결과를 캐시하여 성능을 개선할 수 있습니다.
+ private let sortedLevels = RoutineLevelType.allCases.sorted(by: { $0.id < $1.id }) - let level = RoutineLevelType.allCases.sorted(by: { $0.id < $1.id })[indexPath.row] + let level = sortedLevels[indexPath.row]Projects/Presentation/Sources/Common/Component/CustomBottomSheet.swift (1)
148-148: 매직 넘버 상수화 제안제스처 기반 dismiss 임계값들을 상수로 추출하면 가독성과 유지보수성이 향상됩니다.
+ private enum GestureThreshold { + static let dismissPositionRatio: CGFloat = 0.3 + static let dismissVelocity: CGFloat = 1000 + } - let shouldDismiss = translation.y > maxHeight * 0.3 || velocity.y > 1000 + let shouldDismiss = translation.y > maxHeight * GestureThreshold.dismissPositionRatio || velocity.y > GestureThreshold.dismissVelocityProjects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (3)
195-197: TODO 주석 해결 필요정적 분석 도구가 지적한 대로 TODO 주석이 남아 있습니다. 루틴 등록 화면으로의 네비게이션 구현이 필요합니다.
네비게이션 구현을 위한 코드 생성을 도와드릴까요? 아니면 이를 추적하기 위한 새로운 이슈를 생성해드릴까요?
153-173: 메서드 명명 개선 제안
fetchRecommendedRoutines메서드는 실제로 데이터를 가져오는 것이 아니라 UI를 업데이트하는 역할을 합니다. 메서드명을updateRecommendedRoutinesUI또는configureRecommendedRoutines로 변경하는 것이 더 명확할 것 같습니다.- private func fetchRecommendedRoutines(recommendedRoutines: [RecommendedRoutine]) { + private func updateRecommendedRoutinesUI(recommendedRoutines: [RecommendedRoutine]) {
170-172: 조건부 UI 표시 로직 개선 제안감정 버튼을 표시하는 조건이 배열의 첫 번째 요소만 체크하고 있습니다. 더 안전한 방법을 고려해보세요.
- if !recommendedRoutines.isEmpty && recommendedRoutines[0].routineCategory == .recommendation { + if recommendedRoutines.allSatisfy({ $0.routineCategory == .recommendation }) && !recommendedRoutines.isEmpty { showEmotionButton() }또는 뷰모델의 selectedCategory를 직접 체크하는 방법:
- if !recommendedRoutines.isEmpty && recommendedRoutines[0].routineCategory == .recommendation { + if viewModel.output.selectedCategoryPublisher.value == .recommendation && !recommendedRoutines.isEmpty { showEmotionButton() }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
Projects/Presentation/Resources/Images.xcassets/chevron_icon.imageset/chevron_icon.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_icon.imageset/chevron_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_icon.imageset/chevron_icon@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/plus_icon.imageset/plus_icon.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/plus_icon.imageset/plus_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/plus_icon.imageset/plus_icon@3x.pngis excluded by!**/*.png
📒 Files selected for processing (20)
Projects/Presentation/Resources/Images.xcassets/chevron_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/plus_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/Component/CustomBottomSheet.swift(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift(2 hunks)Projects/Presentation/Sources/Common/Extension/UIImage+.swift(1 hunks)Projects/Presentation/Sources/Common/Extension/UIViewController+.swift(2 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Common/TabBarView.swift(1 hunks)Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineCategoryType.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineLevelType.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelCell.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift(1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
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/Common/PresentationDependencyAssembler.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/Common/TabBarView.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (3)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
🧬 Code Graph Analysis (8)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
register(14-16)
Projects/Presentation/Sources/Common/TabBarView.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
resolve(18-25)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift (1)
Projects/Presentation/Sources/Common/Extension/UIImage+.swift (1)
resizeAspectFit(20-35)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift (1)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift (2)
configureAttribute(38-55)configureLayout(57-80)
Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1)
Projects/Presentation/Sources/Common/Extension/UIImage+.swift (2)
rotate(56-67)rotate(69-71)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (4)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)
presentCustomBottomSheet(79-82)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift (1)
updateSelectedCategory(82-87)Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (2)
selectCategory(49-55)selectLevel(57-60)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift (1)
updateButton(85-87)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift (2)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (1)
recommendedRoutineCardViewDelegate(194-197)Projects/Presentation/Sources/Common/Extension/UIImage+.swift (1)
resizeAspectFit(20-35)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift (2)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (1)
routineCategoryView(187-189)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift (3)
configureAttribute(34-42)configureLayout(44-52)updateButtonState(63-65)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift
[Warning] 23-23: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
[Warning] 195-195: TODOs should be resolved (루틴 등록하기 화면으로 이동해야 함 + 추천 루틴 들고)
(todo)
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift
[Warning] 59-59: TODOs should be resolved (현재 보여주고 있는 추천 루틴 난이도 필터링 해야 함)
(todo)
[Warning] 82-82: TODOs should be resolved (Dummy Data를 지우세요)
(todo)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift
[Warning] 21-21: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
🔇 Additional comments (42)
Projects/Presentation/Resources/Images.xcassets/plus_icon.imageset/Contents.json (1)
1-24: 에셋 카탈로그 구조가 표준을 따르고 있습니다.Xcode 에셋 카탈로그의 표준 JSON 구조를 올바르게 따르고 있으며, 모든 스케일 팩터(1x, 2x, 3x)와 필수 메타데이터가 적절히 정의되어 있습니다.
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
39-41: 의존성 주입 등록이 기존 패턴을 잘 따르고 있습니다.
RecommendedRoutineViewModel의 DI 등록이 기존 패턴과 일치하며 올바르게 구현되어 있습니다. 현재는 의존성 없이 단순 인스턴스 생성만 하고 있는데, 향후 서버 통신 로직이 추가되면 UseCase 의존성 주입을 고려해볼 수 있겠습니다.Projects/Presentation/Resources/Images.xcassets/chevron_icon.imageset/Contents.json (1)
1-24: 에셋 카탈로그 구조가 표준을 따르고 있습니다.Xcode 에셋 카탈로그의 표준 JSON 구조를 올바르게 따르고 있으며, 회전을 통한 방향성 아이콘 구현에 적합한 기본 아이콘으로 잘 설계되어 있습니다.
Projects/Presentation/Sources/Common/TabBarView.swift (1)
45-45: RecommendedRoutineViewModel 의존성 등록이 확인되었습니다.
- Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
•DIContainer.shared.register(type: RecommendedRoutineViewModel.self) { … }구문으로 정상 등록- Projects/Presentation/Sources/Common/TabBarView.swift
•DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self)호출부 정상 작동이대로 머지해도 무방합니다.
Projects/Presentation/Sources/Onboarding/Model/RecommendedRoutine.swift (2)
14-14: 새로운 routineCategory 속성이 추가되었습니다.카테고리 기능을 지원하기 위한 적절한 확장입니다.
16-26: 명시적 초기화 메서드가 잘 구현되었습니다.기본값을
.recommendation으로 설정한 것이 합리적입니다.Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineLevelType.swift (1)
8-28: 루틴 난이도 타입이 잘 설계되었습니다.
CaseIterable프로토콜 준수로 모든 케이스를 쉽게 순회할 수 있습니다id와levelTitle계산 속성이 명확하고 직관적입니다- 한국어 설명이 사용자 친화적으로 작성되었습니다
Projects/Presentation/Sources/RecommendedRoutine/Model/RoutineCategoryType.swift (1)
8-37: 루틴 카테고리 타입이 체계적으로 구현되었습니다.
- 6개의 카테고리가 명확하게 정의되어 있습니다
CaseIterable프로토콜로 UI에서 쉽게 활용할 수 있습니다- 각 카테고리의 한국어 제목이 직관적이고 이해하기 쉽습니다
- 순차적인 ID 값으로 일관성이 유지됩니다
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift (3)
13-20: 레이아웃 상수가 체계적으로 정의되었습니다.Layout enum을 통해 모든 레이아웃 관련 상수를 중앙 집중식으로 관리하는 것이 좋은 패턴입니다.
48-52: 디자인 시스템 일관성이 잘 유지되었습니다.BitnagilFont와 BitnagilColor를 일관되게 사용하여 앱의 디자인 시스템을 잘 따르고 있습니다.
55-69: SnapKit을 활용한 오토레이아웃이 적절히 구현되었습니다.아이콘과 레이블의 배치가 명확하고 제약 조건이 잘 정의되어 있습니다.
Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (3)
18-19: 새로운 아이콘 추가 승인plusIcon과 chevronIcon이 적절하게 추가되었습니다. plusIcon은 template 모드로 설정되어 tintColor 변경이 가능하며, chevronIcon은 기본 모드로 설정되어 있습니다.
20-22: chevronIcon 방향 메서드 구현 승인Direction enum의 rotation 값을 사용하여 chevronIcon을 회전시키는 메서드가 잘 구현되었습니다. 회전 후 template 모드로 설정하여 tintColor 변경이 가능합니다.
38-52: Direction enum 구현 승인Direction enum이 up, down, left, right 케이스와 각각의 회전 각도를 명확하게 정의하고 있습니다. 각 방향별 회전 각도가 직관적으로 설정되어 있습니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelCell.swift (4)
10-28: RoutineLevelCell 클래스 구조 승인UITableViewCell을 상속한 클래스 구조가 적절하게 구현되었습니다. Layout enum으로 상수를 관리하고 있어 유지보수성이 좋습니다.
30-43: UI 속성 설정 승인titleLabel과 checkIcon의 속성이 적절하게 설정되었습니다. 체크 아이콘의 크기 조정과 템플릿 모드 적용이 잘 구현되어 있습니다.
45-59: 레이아웃 제약 조건 승인SnapKit을 사용한 레이아웃 제약 조건이 명확하게 설정되었습니다. titleLabel은 leading에, checkIcon은 trailing에 배치되어 적절한 UI 구조를 형성합니다.
61-64: 셀 설정 메서드 승인configureCell 메서드가 RoutineLevelType과 선택 상태를 받아 셀을 적절히 업데이트합니다. 체크 아이콘의 표시/숨김 처리가 명확합니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift (4)
10-12: delegate 프로토콜 정의 승인RecommendedRoutineCardViewDelegate 프로토콜이 적절하게 정의되었습니다. 델리게이트 메서드 이름이 명확하고 필요한 매개변수를 포함하고 있습니다.
14-43: 클래스 구조 및 초기화 승인RecommendedRoutineCardView 클래스 구조가 적절하게 구현되었습니다. Layout enum으로 상수를 관리하고, 초기화 메서드에서 RecommendedRoutine을 받아 적절히 설정합니다.
45-76: UI 속성 설정 승인UI 요소들의 속성이 적절하게 설정되었습니다. 라벨의 텍스트와 스타일 설정, plusButton의 이미지 및 액션 처리가 잘 구현되어 있습니다. weak self 사용으로 메모리 누수도 방지되었습니다.
78-103: 레이아웃 제약 조건 승인SnapKit을 사용한 레이아웃 제약 조건이 명확하게 설정되었습니다. labelStackView와 plusButton의 배치가 적절하며, 각 요소의 크기와 위치가 잘 정의되어 있습니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryButton.swift (5)
11-32: RoutineCategoryButton 클래스 구조 승인UIButton을 상속한 클래스 구조가 적절하게 구현되었습니다. isChecked 프로퍼티의 didSet을 통한 자동 업데이트 메커니즘이 잘 설계되어 있습니다.
34-42: UI 속성 초기 설정 승인배경색과 카테고리 라벨의 초기 속성이 적절하게 설정되었습니다. 폰트와 색상이 디자인 시스템을 따르고 있습니다.
44-52: 레이아웃 설정 승인코너 반지름과 라벨 중앙 정렬이 적절하게 구현되었습니다. 버튼 높이의 절반으로 설정한 코너 반지름이 캡슐 모양을 만듭니다.
54-61: 버튼 상태 업데이트 로직 승인체크 상태에 따른 배경색, 폰트, 텍스트 색상 변경 로직이 명확하게 구현되었습니다. 선택된 상태와 비선택 상태의 스타일 차이가 적절합니다.
63-65: 외부 상태 업데이트 메서드 승인updateButtonState 메서드를 통해 외부에서 버튼 상태를 업데이트할 수 있도록 인터페이스가 제공되어 있습니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelButton.swift (4)
10-37: RoutineLevelButton 클래스 구조 승인UIButton을 상속한 클래스 구조가 적절하게 구현되었습니다. Layout enum으로 상수를 관리하고, level 프로퍼티의 didSet을 통한 자동 업데이트 메커니즘이 잘 설계되어 있습니다.
39-60: UI 속성 설정 승인스택뷰, 라벨, 시브론 아이콘의 속성이 적절하게 설정되었습니다. BitnagilIcon.chevronIcon(direction: .down) 사용과 이미지 크기 조정이 잘 구현되어 있습니다.
62-79: 레이아웃 제약 조건 승인SnapKit을 사용한 레이아웃 제약 조건이 명확하게 설정되었습니다. 스택뷰의 inset과 각 요소의 크기 설정이 적절합니다.
81-88: 버튼 업데이트 메서드 승인updateButtonLabel과 updateButton 메서드가 적절하게 구현되었습니다. 외부에서 레벨을 업데이트할 수 있는 인터페이스가 제공되어 있습니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineCategoryView.swift (2)
26-26: 델리게이트 패턴 구현 우수
weak var delegate를 사용하여 retain cycle을 방지한 것이 좋습니다. 델리게이트 패턴 구현이 적절합니다.
82-87: UI 상태 업데이트 로직 우수선택된 카테고리에 따라 각 버튼의 체크 상태를 업데이트하는 로직이 명확하고 효율적입니다.
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (1)
70-78: 선택 토글 로직 우수동일한 레벨을 선택하면 선택 해제하는 토글 로직이 사용자 경험에 좋습니다. 선택 후 즉시 dismiss하는 것도 적절합니다.
Projects/Presentation/Sources/Common/Component/CustomBottomSheet.swift (3)
22-33: 초기화 및 모달 설정 우수
overFullScreen모달 스타일과crossDissolve트랜지션이 바텀시트 UX에 적절하게 설정되어 있습니다.
107-115: 자식 뷰 컨트롤러 관리 우수
addChild,didMove(toParent:)메서드를 사용한 적절한 뷰 컨트롤러 컨테인먼트 구현입니다.
135-162: 제스처 처리 로직 우수팬 제스처의 velocity와 position 기반 dismiss 로직이 매우 정교하게 구현되어 있습니다. 인터랙티브한 사용자 경험을 제공합니다.
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (2)
137-151: Combine 바인딩 구현 우수
weak self를 사용한 메모리 안전한 바인딩과 메인 스레드에서의 UI 업데이트가 올바르게 구현되어 있습니다.
185-206: 델리게이트 패턴 구현 우수세 가지 델리게이트 프로토콜을 통한 깔끔한 컴포넌트 간 통신이 잘 구현되어 있습니다.
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift (3)
10-34: Input/Output 패턴 및 Combine 사용 우수ViewModel 프로토콜과 Combine을 활용한 깔끔한 Input/Output 패턴이 잘 구현되어 있습니다. 반응형 프로그래밍의 좋은 예시입니다.
49-55: 카테고리 선택 로직 우수현재 카테고리와 비교하여 불필요한 업데이트를 방지하는 로직이 효율적입니다.
59-60: 난이도 필터링 모델 미지원 안내
현재 Onboarding 및 Domain 모델(RecommendedRoutine, RecommendedRoutineEntity, RecommendedRoutineDTO)에는 난이도(difficulty/level) 관련 프로퍼티가 정의되어 있지 않습니다. 서버 API가 제공되는 시점에 해당 필드를 추가하고 ViewModel에서 필터링 로직을 구현하시면 됩니다.
이 PR 범위에서는 아래 TODO를 남겨두고, API 연동 이후 구현하는 것으로 충분해 보입니다. 전체 구조(Input/Output 패턴, Combine 사용)도 깔끔합니다.
- TODO: 난이도 프로퍼티 추가 및 ViewModel에서 레벨 필터링 구현 (API 연동 시점)
- TODO: 더미 데이터 제거 및 리팩토링
| guard let viewModel = DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self) else { | ||
| fatalError("RecommendedViewModel 의존성이 등록되지 않았습니다.") | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
의존성 해결 실패 시 더 안전한 에러 처리 방식을 고려해보세요.
fatalError를 사용하면 앱이 크래시되므로, 사용자 경험에 부정적인 영향을 줄 수 있습니다. 이전 학습 내용에 따르면 추후 CustomAlertView를 통한 에러 처리를 계획하고 계신 것으로 보이니, 현재는 임시적인 처리 방식으로 이해하겠습니다.
- guard let viewModel = DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self) else {
- fatalError("RecommendedViewModel 의존성이 등록되지 않았습니다.")
- }
+ guard let viewModel = DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self) else {
+ BitnagilLogger.log(logType: .error, message: "RecommendedRoutineViewModel 의존성이 등록되지 않았습니다.")
+ // 임시 처리: 기본 뷰 또는 에러 뷰 표시
+ return
+ }📝 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.
| guard let viewModel = DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self) else { | |
| fatalError("RecommendedViewModel 의존성이 등록되지 않았습니다.") | |
| } | |
| guard let viewModel = DIContainer.shared.resolve(type: RecommendedRoutineViewModel.self) else { | |
| BitnagilLogger.log(logType: .error, message: "RecommendedRoutineViewModel 의존성이 등록되지 않았습니다.") | |
| // 임시 처리: 기본 뷰 또는 에러 뷰 표시 | |
| return | |
| } |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Common/TabBarView.swift around lines 41 to 43,
replace the fatalError call used when dependency resolution for
RecommendedRoutineViewModel fails with a safer error handling approach. Instead
of crashing the app, implement a mechanism to show a user-friendly error
message, such as displaying a CustomAlertView or logging the error gracefully,
to improve user experience while maintaining app stability.
| plusIcon.do { | ||
| $0.contentMode = .center | ||
| $0.image = BitnagilIcon.plusIcon? | ||
| .resizeAspectFit(to: CGSize(width: Layout.plusIconImageSize, height: Layout.plusIconImageSize))? | ||
| .withRenderingMode(.alwaysTemplate) | ||
| $0.tintColor = BitnagilColor.navy400 | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이미지 리사이징 체인에서 잠재적인 nil 처리 문제가 있습니다.
resizeAspectFit 메서드가 nil을 반환할 수 있어서, 체인 중간에 실패하면 아이콘이 표시되지 않을 수 있습니다.
- $0.image = BitnagilIcon.plusIcon?
- .resizeAspectFit(to: CGSize(width: Layout.plusIconImageSize, height: Layout.plusIconImageSize))?
- .withRenderingMode(.alwaysTemplate)
+ if let originalImage = BitnagilIcon.plusIcon,
+ let resizedImage = originalImage.resizeAspectFit(to: CGSize(width: Layout.plusIconImageSize, height: Layout.plusIconImageSize)) {
+ $0.image = resizedImage.withRenderingMode(.alwaysTemplate)
+ } else {
+ $0.image = BitnagilIcon.plusIcon?.withRenderingMode(.alwaysTemplate)
+ }🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RegisterEmotionButton.swift
around lines 40 to 46, the image resizing chain uses optional chaining that may
result in a nil image if resizeAspectFit returns nil, causing the icon not to
display. To fix this, safely unwrap the result of resizeAspectFit before
applying withRenderingMode and setting the image, ensuring the image is only set
if resizing succeeds.
| // TODO: Dummy Data를 지우세요 | ||
| private let recommendationRoutines: [RecommendedRoutine] = [ | ||
| RecommendedRoutine(id: 1, mainTitle: "쓰레기 버리러 나가기", subTitle: "간단한 외출도 의미있는 변화예요."), | ||
| RecommendedRoutine(id: 2, mainTitle: "산책하며 노란 물건 찾아보기", subTitle: "가까운 공원까지만 나가도 상쾌해져요."), | ||
| RecommendedRoutine(id: 3, mainTitle: "밤 산책하며 노후 가로등 찾기", subTitle: "빛이 희미한 가로등이 있다면 제보해봐요."), | ||
| RecommendedRoutine(id: 4, mainTitle: "산책하며 고장난 표지판 찾기", subTitle: "훼손된 표지판을 제보해봐요.") | ||
| ] | ||
|
|
||
| private let outdoorRoutines: [RecommendedRoutine] = [ | ||
| RecommendedRoutine(id: 1, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor), | ||
| RecommendedRoutine(id: 2, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor), | ||
| RecommendedRoutine(id: 3, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor), | ||
| RecommendedRoutine(id: 4, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor) | ||
| ] | ||
|
|
||
| private let wakeupRoutines: [RecommendedRoutine] = [ | ||
| RecommendedRoutine(id: 1, mainTitle: "일어나요", subTitle: "일어나라꼬!!!", routineCategory: .wakeup), | ||
| RecommendedRoutine(id: 2, mainTitle: "일어나요", subTitle: "일어나라꼬!!!", routineCategory: .wakeup) | ||
| ] | ||
|
|
||
| private let connectionRoutines: [RecommendedRoutine] = [ | ||
| RecommendedRoutine(id: 1, mainTitle: "연결해요", subTitle: "연결하라꼬!!!", routineCategory: .connection) | ||
| ] | ||
|
|
||
| private let restRoutines: [RecommendedRoutine] = [ | ||
| RecommendedRoutine(id: 1, mainTitle: "쉬어가요", subTitle: "쉬어갑시다아 ~~~ 아아아 ~~", routineCategory: .rest) | ||
| ] | ||
|
|
||
| private let growthRoutines: [RecommendedRoutine] = [ | ||
| RecommendedRoutine(id: 1, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 2, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 3, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 4, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 5, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 6, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 7, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 8, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 9, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 10, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 11, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| RecommendedRoutine(id: 12, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | ||
| ] |
There was a problem hiding this comment.
🛠️ Refactor suggestion
더미 데이터 일관성 개선 제안
더미 데이터에서 routineCategory 프로퍼티가 일부 누락되어 있습니다. 일관성을 위해 모든 더미 데이터에 추가하는 것을 고려해보세요.
private let recommendationRoutines: [RecommendedRoutine] = [
- RecommendedRoutine(id: 1, mainTitle: "쓰레기 버리러 나가기", subTitle: "간단한 외출도 의미있는 변화예요."),
+ RecommendedRoutine(id: 1, mainTitle: "쓰레기 버리러 나가기", subTitle: "간단한 외출도 의미있는 변화예요.", routineCategory: .recommendation),
// ... 다른 항목들도 동일하게 수정
]📝 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.
| // TODO: Dummy Data를 지우세요 | |
| private let recommendationRoutines: [RecommendedRoutine] = [ | |
| RecommendedRoutine(id: 1, mainTitle: "쓰레기 버리러 나가기", subTitle: "간단한 외출도 의미있는 변화예요."), | |
| RecommendedRoutine(id: 2, mainTitle: "산책하며 노란 물건 찾아보기", subTitle: "가까운 공원까지만 나가도 상쾌해져요."), | |
| RecommendedRoutine(id: 3, mainTitle: "밤 산책하며 노후 가로등 찾기", subTitle: "빛이 희미한 가로등이 있다면 제보해봐요."), | |
| RecommendedRoutine(id: 4, mainTitle: "산책하며 고장난 표지판 찾기", subTitle: "훼손된 표지판을 제보해봐요.") | |
| ] | |
| private let outdoorRoutines: [RecommendedRoutine] = [ | |
| RecommendedRoutine(id: 1, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor), | |
| RecommendedRoutine(id: 2, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor), | |
| RecommendedRoutine(id: 3, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor), | |
| RecommendedRoutine(id: 4, mainTitle: "나가봐요", subTitle: "나가라꼬!!!", routineCategory: .outdoor) | |
| ] | |
| private let wakeupRoutines: [RecommendedRoutine] = [ | |
| RecommendedRoutine(id: 1, mainTitle: "일어나요", subTitle: "일어나라꼬!!!", routineCategory: .wakeup), | |
| RecommendedRoutine(id: 2, mainTitle: "일어나요", subTitle: "일어나라꼬!!!", routineCategory: .wakeup) | |
| ] | |
| private let connectionRoutines: [RecommendedRoutine] = [ | |
| RecommendedRoutine(id: 1, mainTitle: "연결해요", subTitle: "연결하라꼬!!!", routineCategory: .connection) | |
| ] | |
| private let restRoutines: [RecommendedRoutine] = [ | |
| RecommendedRoutine(id: 1, mainTitle: "쉬어가요", subTitle: "쉬어갑시다아 ~~~ 아아아 ~~", routineCategory: .rest) | |
| ] | |
| private let growthRoutines: [RecommendedRoutine] = [ | |
| RecommendedRoutine(id: 1, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 2, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 3, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 4, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 5, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 6, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 7, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 8, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 9, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 10, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 11, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| RecommendedRoutine(id: 12, mainTitle: "성장해요", subTitle: "성.장.했.다", routineCategory: .growth), | |
| ] | |
| // TODO: Dummy Data를 지우세요 | |
| private let recommendationRoutines: [RecommendedRoutine] = [ | |
| RecommendedRoutine(id: 1, mainTitle: "쓰레기 버리러 나가기", subTitle: "간단한 외출도 의미있는 변화예요.", routineCategory: .recommendation), | |
| RecommendedRoutine(id: 2, mainTitle: "산책하며 노란 물건 찾아보기", subTitle: "가까운 공원까지만 나가도 상쾌해져요.", routineCategory: .recommendation), | |
| RecommendedRoutine(id: 3, mainTitle: "밤 산책하며 노후 가로등 찾기", subTitle: "빛이 희미한 가로등이 있다면 제보해봐요.", routineCategory: .recommendation), | |
| RecommendedRoutine(id: 4, mainTitle: "산책하며 고장난 표지판 찾기", subTitle: "훼손된 표지판을 제보해봐요.", routineCategory: .recommendation) | |
| ] |
🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 82-82: TODOs should be resolved (Dummy Data를 지우세요)
(todo)
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RecommendedRoutine/ViewModel/RecommendedRoutineViewModel.swift
between lines 82 and 123, the dummy data arrays have inconsistent usage of the
routineCategory property; some RecommendedRoutine instances lack this property.
To fix this, add the routineCategory property with the appropriate category
value to all RecommendedRoutine instances to ensure consistency across all dummy
data entries.
There was a problem hiding this comment.
RecommendedRoutine init시에 routineCategory: .recommendation를 default로 잡아주고 있어서 괜찮지 않을까요 ??
또한 더미데이터이므로 추후 서버 로직 붙이면서 삭제될 예정이라 수정할 필요 없을 것 같습니다.
There was a problem hiding this comment.
@choijungp 맞습니다! RecommendedRoutine 초기화 시 routineCategory가 .recommendation으로 기본값 설정되어 있어서 명시적으로 지정하지 않아도 정상 동작하겠네요.
그리고 더미 데이터는 임시적이고 서버 로직 연동 시 삭제될 예정이니까 굳이 수정할 필요가 없다는 점도 동의합니다. 실용적인 접근이네요!
설명해주셔서 감사합니다. 🙂
🧠 Learnings used
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
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 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
taipaise
left a comment
There was a problem hiding this comment.
고생하셨습니다 진짜 UIKit gosu가 되셧군요.. 열심히 따라가볼게요 🥹
There was a problem hiding this comment.
고생하셨습니다!! 혹시 sheetPresentationController를 고려해보셨을까요?
따로 커스텀 바텀 시트를 VC를 구현하신 이유가 있는지 궁금합니다!
There was a problem hiding this comment.
엇 혹시 sheetPresentationController 이것도 MaxHeight를 설정할 수 있나요 ??
디자인 요구사항에서 딱 최대 높이 이상은 못올라가게 했으면 좋겠다는 의견이 있어서 CustomBottomSheet를 구현했습니다 !! ㅠㅠ
There was a problem hiding this comment.
제가 알기론느 detent를 이용해서 최대 높이를 정해줄 수 있는것으로 알고 있습니다! 다만 iOS 버전에 따라 조금 다를 수 있을거 같아서 확인이 필요할 것 같네요!
그런 제약 조건이 있는걸 몰랐어요.!.! 커스텀 시트 구현해주셔서 오히려 좋아입니다~
| import UIKit | ||
|
|
||
| protocol RecommendedRoutineCardViewDelegate: AnyObject { | ||
| func recommendedRoutineCardViewDelegate(_ sender: RecommendedRoutineCardView, didTapRecommendedRoutine routine: RecommendedRoutine) |
There was a problem hiding this comment.
프로토콜 이름에 이미 RecommendedRoutineCardView에 대한 Delegate라는 의미가 포함되어 있어서,
메서드 이름에서는 Delegate라는 단어를 생략해도 충분히 의미가 전달될 것 같습니다!
| func recommendedRoutineCardViewDelegate(_ sender: RecommendedRoutineCardView, didTapRecommendedRoutine routine: RecommendedRoutine) | |
| func recommendedRoutineCardView(_ sender: RecommendedRoutineCardView, didTapRecommendedRoutine routine: RecommendedRoutine) |
- 밑에서는 생략하신 것을 보니 진짜 단순 실수인거 같아요!!!
There was a problem hiding this comment.
헐 !! 땡큐베리감사 !!!! 7df9948 커밋에서 수정했습니다 !!
토끼도 못잡은 것을 잡다니 ... 🥺👍🏻 감사합니다 !!!
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (1)
21-26: 중복 초기화 제거 및 성능 최적화 필요정적 분석 도구가 지적한 대로
nil로 초기화하는 것은 중복입니다. 또한didSet에서reloadData()와 델리게이트 호출을 동시에 하면 성능에 영향을 줄 수 있습니다.- private var selectedLevel: RoutineLevelType? = nil { + private var selectedLevel: RoutineLevelType? { didSet { - levelTableView.reloadData() delegate?.routineLevelView(self, didSelectLevel: selectedLevel) } }그리고
didSelectRowAt에서 수동으로reloadData()호출:public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // ... existing selection logic ... + levelTableView.reloadData() dismiss(animated: true) }
🧹 Nitpick comments (5)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (2)
59-59: 중복된 정렬 로직 제거 필요동일한 정렬 로직이 두 곳에서 반복되고 있습니다. 성능 최적화를 위해 computed property로 추출하는 것을 권장합니다.
+ private var sortedLevels: [RoutineLevelType] { + return RoutineLevelType.allCases.sorted(by: { $0.id < $1.id }) + } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineLevelCell", for: indexPath) as? RoutineLevelCell else { return UITableViewCell() } - let level = RoutineLevelType.allCases.sorted(by: { $0.id < $1.id })[indexPath.row] + let level = sortedLevels[indexPath.row] let isSelected = selectedLevel == level cell.configureCell(level: level, isSelected: isSelected) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let selectedLevel = RoutineLevelType.allCases.sorted(by: { $0.id < $1.id })[indexPath.row] + let selectedLevel = sortedLevels[indexPath.row] if self.selectedLevel == selectedLevel { self.selectedLevel = nil } else { self.selectedLevel = selectedLevel } dismiss(animated: true) }Also applies to: 71-71
38-38: 셀 식별자 상수화 권장하드코딩된 문자열 대신 상수로 관리하는 것이 유지보수성 측면에서 더 좋습니다.
+ private enum Constants { + static let cellIdentifier = "RoutineLevelCell" + } private func configureAttribute() { levelTableView.delegate = self levelTableView.dataSource = self - levelTableView.register(RoutineLevelCell.self, forCellReuseIdentifier: "RoutineLevelCell") + levelTableView.register(RoutineLevelCell.self, forCellReuseIdentifier: Constants.cellIdentifier) } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "RoutineLevelCell", for: indexPath) as? RoutineLevelCell + guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellIdentifier, for: indexPath) as? RoutineLevelCell else { return UITableViewCell() }Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (3)
194-197: TODO 주석 해결 필요루틴 등록 화면으로의 네비게이션 로직이 구현되지 않았습니다. 현재 임시로 print문만 사용하고 있습니다.
이 TODO를 해결하기 위한 이슈를 생성하거나 구현 코드를 제공해드릴까요?
39-43: 초기화 최적화 제안
cancellables초기화를 더 간결하게 할 수 있습니다.- private var cancellables: Set<AnyCancellable> + private var cancellables = Set<AnyCancellable>() public override init(viewModel: RecommendedRoutineViewModel) { - cancellables = [] super.init(viewModel: viewModel) }
153-173: 메모리 효율성 및 가독성 개선현재 구현에서 개선할 수 있는 부분들이 있습니다.
private func fetchRecommendedRoutines(recommendedRoutines: [RecommendedRoutine]) { + clearExistingRoutines() + setupRoutineCards(recommendedRoutines) + + if shouldShowEmotionButton(recommendedRoutines) { + showEmotionButton() + } + } + + private func clearExistingRoutines() { recommendedRoutineStackView.arrangedSubviews.forEach { view in recommendedRoutineStackView.removeArrangedSubview(view) view.removeFromSuperview() } recommendedRoutineCards.removeAll() + } + + private func setupRoutineCards(_ recommendedRoutines: [RecommendedRoutine]) { for routine in recommendedRoutines { let routineCard = RecommendedRoutineCardView(recommendedRoutine: routine) recommendedRoutineCards[routine.id] = routineCard recommendedRoutineStackView.addArrangedSubview(routineCard) routineCard.delegate = self routineCard.snp.makeConstraints { make in make.height.equalTo(Layout.routineCardHeight) } } + } + + private func shouldShowEmotionButton(_ recommendedRoutines: [RecommendedRoutine]) -> Bool { + return !recommendedRoutines.isEmpty && recommendedRoutines[0].routineCategory == .recommendation + } - if !recommendedRoutines.isEmpty && recommendedRoutines[0].routineCategory == .recommendation { - showEmotionButton() - } }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Projects/Presentation/Sources/RecommendedRoutine/View/Component/RecommendedRoutineCardView.swift
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
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/RecommendedRoutine/View/Component/RoutineLevelView.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (3)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
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()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift
[Warning] 21-21: Initializing an optional variable with nil is redundant
(redundant_optional_initialization)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
[Warning] 195-195: 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 (2)
Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift (2)
137-151: 메모리 누수 방지 확인Combine 구독 관리가 적절히 구현되어 있습니다.
weak self를 사용하여 강한 참조 순환을 방지하고 있습니다.
85-87: 바텀시트 프레젠테이션 구현 확인새로운
CustomBottomSheet컴포넌트를 적절히 활용하고 있습니다. 사용자 경험 측면에서 바텀시트 높이 설정도 적절합니다.
- RoutineLevelView 테이블 뷰 셀 밑줄 간격 조정 및 마지막 셀 밑줄 생략 - CustomBottomSheet 투명도 변경
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (1)
21-26: 중복 초기화 제거 및 성능 최적화 필요정적 분석 도구가 지적한 대로
nil로 초기화하는 것은 중복입니다. 또한didSet에서 델리게이트 호출을 바로 하면 성능에 영향을 줄 수 있습니다.- private var selectedLevel: RoutineLevelType? = nil { + private var selectedLevel: RoutineLevelType? { didSet { - levelTableView.reloadData() delegate?.routineLevelView(self, didSelectLevel: selectedLevel) } }그리고
didSelectRowAt에서 수동으로reloadData()호출:public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // ... existing selection logic ... + levelTableView.reloadData() dismiss(animated: true) }
🧹 Nitpick comments (2)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (2)
70-78: 캐시된 정렬 결과를 일관되게 사용하세요.다른 메서드들과 일관성을 위해 캐시된
sortedLevels를 사용하세요.func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - let isLastCell = indexPath.row == RoutineLevelType.allCases.count - 1 + let isLastCell = indexPath.row == sortedLevels.count - 1
80-90: 캐시된 정렬 결과를 사용하여 일관성을 유지하세요.다른 메서드들과 마찬가지로 캐시된
sortedLevels를 사용하세요.func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let selectedLevel = RoutineLevelType.allCases.sorted(by: { $0.id < $1.id })[indexPath.row] + let selectedLevel = sortedLevels[indexPath.row] if self.selectedLevel == selectedLevel { self.selectedLevel = nil } else { self.selectedLevel = selectedLevel } levelTableView.reloadData() dismiss(animated: true) }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Projects/Presentation/Sources/Common/Component/CustomBottomSheet.swift(1 hunks)Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Projects/Presentation/Sources/Common/Component/CustomBottomSheet.swift
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
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/RecommendedRoutine/View/Component/RoutineLevelView.swift (1)
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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
🔇 Additional comments (1)
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift (1)
10-12: 델리게이트 프로토콜 정의가 적절합니다.Swift 네이밍 컨벤션을 잘 따르고 있고, 메서드 시그니처가 명확합니다.
| private func configureAttribute() { | ||
| levelTableView.delegate = self | ||
| levelTableView.dataSource = self | ||
| levelTableView.register(RoutineLevelCell.self, forCellReuseIdentifier: "RoutineLevelCell") | ||
| } | ||
|
|
||
| private func configureLayout() { | ||
| view.addSubview(levelTableView) | ||
|
|
||
| levelTableView.snp.makeConstraints { make in | ||
| make.edges.equalToSuperview() | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
셀 식별자를 상수로 정의하여 안전성을 높이세요.
문자열 리터럴을 직접 사용하면 오타로 인한 런타임 에러가 발생할 수 있습니다.
+ private enum Constants {
+ static let cellIdentifier = "RoutineLevelCell"
+ }
private func configureAttribute() {
levelTableView.delegate = self
levelTableView.dataSource = self
- levelTableView.register(RoutineLevelCell.self, forCellReuseIdentifier: "RoutineLevelCell")
+ levelTableView.register(RoutineLevelCell.self, forCellReuseIdentifier: Constants.cellIdentifier)
}📝 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 func configureAttribute() { | |
| levelTableView.delegate = self | |
| levelTableView.dataSource = self | |
| levelTableView.register(RoutineLevelCell.self, forCellReuseIdentifier: "RoutineLevelCell") | |
| } | |
| private func configureLayout() { | |
| view.addSubview(levelTableView) | |
| levelTableView.snp.makeConstraints { make in | |
| make.edges.equalToSuperview() | |
| } | |
| } | |
| private enum Constants { | |
| static let cellIdentifier = "RoutineLevelCell" | |
| } | |
| private func configureAttribute() { | |
| levelTableView.delegate = self | |
| levelTableView.dataSource = self | |
| levelTableView.register(RoutineLevelCell.self, forCellReuseIdentifier: Constants.cellIdentifier) | |
| } | |
| private func configureLayout() { | |
| view.addSubview(levelTableView) | |
| levelTableView.snp.makeConstraints { make in | |
| make.edges.equalToSuperview() | |
| } | |
| } |
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RecommendedRoutine/View/Component/RoutineLevelView.swift
around lines 35 to 47, the cell reuse identifier "RoutineLevelCell" is used as a
string literal, which risks runtime errors due to typos. Define this identifier
as a private constant string at the top of the class or file, then replace all
occurrences of the literal with this constant to improve safety and
maintainability.
🌁 Background
추천 루틴 UI를 구현하였습니다.
아직 서버 API가 미배포된 상황으로 현재는 UI와 사용자 인터랙션만 확인해주시면 감사하겠습니다.
또한
CustomBottomSheet를 구현하였습니다.CustomBottomSheet에 원하는 UIViewController를 넣어주시면 됩니다.📱 Screenshot
추천 루틴 목록 확인 (맞춤 추천)
추천 루틴 목록 확인 (맞춤 추천이 아닌 카테고리)
추천 루틴 난이도 선택
👩💻 Contents
추천 루틴 관련 UI
RoutineCategoryButton: 루틴 카테고리 버튼RoutineCategoryView: 전체 루틴 카테고리 버튼들을 보여주는 수평 스크롤 뷰RoutineLevelButton: 루틴 난이도 선택 버튼RoutineLevelCell: RoutineLevelView에 보일 tableView CellRoutineLevelView: 루틴 난이도 선택 뷰RecommendedRoutineCardView: 추천 루틴 카드 뷰RegisterEmotionButton: 오늘 감정 루틴 추천 받기 버튼RecommendedRoutineView: 추천 루틴 뷰CustomBottomSheet: 커스텀 바텀 시트 (재사용성 높음)✅ Testing
SceneDelegate의 진입점을 TabBarView()로 바꾸어 추천 루틴 탭의 UI를 테스트 해보았습니다.
📝 Review Note
RecommendedRoutine타입에 난이도가 아직 없어 난이도 필터링은 구현하지 않았습니다.추후 서버 통신 로직과 붙일 때 마저 구현하겠습니다.
📣 Related Issue
Summary by CodeRabbit
신규 기능
버그 수정