Skip to content

[Feat-T3-154] 온보딩 디자인 및 서버 v2 수정#46

Merged
choijungp merged 14 commits intodevelopfrom
feat/onboarding-view
Aug 14, 2025
Merged

[Feat-T3-154] 온보딩 디자인 및 서버 v2 수정#46
choijungp merged 14 commits intodevelopfrom
feat/onboarding-view

Conversation

@choijungp
Copy link
Copy Markdown
Contributor

@choijungp choijungp commented Aug 13, 2025

🌁 Background

Splash ~ Onboarding 흐름에 대한 수정된 디자인과 서버를 반영하였습니다.
이르케 큰 작업이 될 줄 몰랐어요 ㅠㅠ

하지만 대부분 View에 관련된 PR이니 슥 ... 훑어만 봐주셔요 ㅠㅠ
미미감 ... 미리 미안하고 감사함니다


📱 Screenshot

로그인 화면 (LoginViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
login - iPhone SE (3rd generation) - 2025-08-11 at 22 32 05 login - iPhone 13 mini - 2025-08-11 at 22 35 16 login - iPhone 16 Pro - 2025-08-11 at 22 34 01

약관 동의 화면 (TermsAgreementViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
agree - iPhone SE (3rd generation) - 2025-08-11 at 23 39 42 agree - iPhone 13 mini - 2025-08-11 at 23 35 55 agree - iPhone 16 Pro - 2025-08-11 at 23 37 42

인트로 화면 (IntroViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
Simulator Screenshot - iPhone SE (3rd generation) - 2025-08-13 at 16 51 52 Simulator Screenshot - iPhone 13 mini - 2025-08-13 at 16 51 22 Simulator Screenshot - iPhone 16 Pro - 2025-08-13 at 16 50 49

온보딩 화면 - 시간대 (OnboardingViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
onboarding1 - iPhone SE (3rd generation) - 2025-08-13 at 16 36 01 onboarding1 - iPhone 13 mini - 2025-08-13 at 16 37 44 onboarding1 - iPhone 16 Pro - 2025-08-13 at 16 43 57

온보딩 화면 - 감정 (OnboardingViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
onboarding2 - iPhone SE (3rd generation) - 2025-08-13 at 16 36 07 onboarding2 - iPhone 13 mini - 2025-08-13 at 16 37 48 onboarding2 - iPhone 16 Pro - 2025-08-13 at 16 44 36

온보딩 화면 - 바깥 바람 (OnboardingViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
onboarding3 - iPhone SE (3rd generation) - 2025-08-13 at 16 36 11 onboarding3 - iPhone 13 mini - 2025-08-13 at 16 37 52 onboarding3 - iPhone 16 Pro - 2025-08-13 at 16 44 52

온보딩 화면 - 외출 목표 (OnboardingViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
onboarding4 - iPhone SE (3rd generation) - 2025-08-13 at 16 36 17 onboarding4 - iPhone 13 mini - 2025-08-13 at 16 37 58 onboarding4 - iPhone 16 Pro - 2025-08-13 at 16 45 11

온보딩 선택지 요약 화면 (OnboardingResultViewController)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
onboarding result - iPhone SE (3rd generation) - 2025-08-13 at 16 36 21 onboarding result - iPhone 13 mini - 2025-08-13 at 16 40 22 onboarding result - iPhone 16 Pro - 2025-08-13 at 16 45 14

온보딩 선택지에 따른 추천 루틴 화면 (ResultRecommendedRoutineView)

iPhone SE3 iPhone 13 mini iPhone 16 Pro
onboarding routine - iPhone SE (3rd generation) - 2025-08-13 at 16 36 26 onboarding routine - iPhone 13 mini - 2025-08-13 at 16 40 27 onboarding routine - iPhone 16 Pro - 2025-08-13 at 16 45 47

👩‍💻 Contents

  • View에 관련된 네이밍 ViewController로 수정
  • View 흐름 변경
    • 기존 : Splash > Intro > Login > Agreement > Onboardings ···
    • 수정 후 : Splash > Login > Agreement > Intro > Onboardings ···
  • CustomNavigationBar UI 구현

📝 Review Note

1. UserState에 따른 화면 분기처리

guest onboarding user
앱 진입 시 Login Intro TabBar
로그인 화면 이후 Agree Intro TabBar

따라서 LoginViewController, IntroViewController만 public으로 두었습니다 !!
로그아웃, 탈퇴 이후에도 원래는 Intro 화면으로 이동하였다면 수정 이후에는 Login 화면으로 이동하도록 수정하였습니다.

혹시 제가 잘못 생각한 분기처리라면 말씀해주세용 ~

2. CustomNavigationBar

기존 UIViewController+에서 구현한 configureNavigationBar 함수 대신 새로운 configureCustomNaviagtionBar 함수를 만들었습니다.
수정된 UI에서는 백버튼도 지글지글 질감처리가 되어야 하기 때문에 새로운 UIView를 생성하는 걸로 구현했습니다 ! !

현재 Figma에서 확인할 수 있는 백버튼 유형에 맞게 enum을 만들어놨습니다.

enum NavigationBarStyle {
    case withBackButton(title: String)  // 백버튼 + 타이틀 (없으면 빈 String)
    case withTitle(title: String)       // 오로지 타이틀만
    case withProgressBar(step: Int)     // 백버튼 + progress
}

그리고 사용하는 View에서는 저는 요론 식으로 사용했습니다.

override func configureLayout() {
    /// ....
    navigationController?.setNavigationBarHidden(true, animated: false)
    configureCustomNaviagtionBar(navigationBarStyle: .withProgressBar(step: onboarding.step))
    /// ....
}

이때 다음 UI 컴포넌트 layout 잡을 때 상단 기준에 유의해야 한다는 단점이 있어용 ㅠ.ㅠ

3. 서버 v1, v2 버전에 따른 루틴 안보임 이슈 ..

홈 화면에서 조회를 하는 api는 현재 v1 기준으로 동작하고, 온보딩 선택지를 통해 받은 추천 루틴 등록은 v2로 합니다!
따라서 현재 온보딩 추천 루틴 등록은 성공하지만 화면에서 해당 루틴이 보이지 않어요 ㅜㅜ

하지만 그것은 홈 화면 수정하면서 보이게 될 것입니다 .....


📣 Related Issue

  • close #T3-154

Summary by CodeRabbit

  • New Features

    • 스플래시 후 로그인·인트로·온보딩 루트 흐름 개편 및 게스트 전용 로그인 경로 추가
    • 닉네임 반영 인트로 화면(IntroViewController/IntroViewModel) 추가 및 온보딩 결과 화면 개선
  • UI/UX

    • 커스텀 내비게이션 바(진행도 표시) 도입 및 여러 화면의 네비게이션 처리 정비
    • 버튼·체크 요소 색상 테마를 오렌지·그레이로 변경
    • 온보딩 관련 컴포넌트 레이아웃·아이콘·그래픽 대규모 추가/교체
  • Chores

    • 온보딩 엔드포인트 버전 분기 및 리소스 번들 로드 경로 조정

- Check Icon 이미지 Asset 교체
- TermsAgreementItemView, TermsAgreementItemView UI 수정
- TermsAgreementViewController 네이밍 및 UI 수정
- IntroViewController 네이밍 수정
- IntroViewController 변경된 디자인 반영
- IntroViewController 기기 크기 대៖
- CustomNavigationBar 구현
- 기존 UIViewController에서 구현된 configureNavigationBar 삭제
- OnboardingType 워딩 수정
- OnboardingChoiceButton, OnboardingViewController, 등 수정
@choijungp choijungp requested a review from taipaise August 13, 2025 08:31
@choijungp choijungp self-assigned this Aug 13, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 13, 2025

Walkthrough

앱 시작·인증·온보딩 플로우가 재구성되었습니다. SplashView가 SplashViewController로 변경되고 SceneDelegate의 델리게이트 시그니처 및 분기가 업데이트되었습니다. 로그인/인트로/온보딩 관련 뷰·뷰모델과 DI 등록, 디자인 시스템(아이콘/그래픽/색상/에셋)이 대규모로 추가·수정·대체되었습니다.

Changes

Cohort / File(s) Summary
앱 시작 및 스플래시
Projects/App/Sources/SceneDelegate.swift, Projects/Presentation/Sources/Common/View/SplashViewController.swift
SplashView → SplashViewController로 타입/델리게이트 시그니처 변경. SceneDelegate의 splash 완료 분기: .user→TabBar, .onboarding→IntroViewController, guest/nil→LoginViewController(네비게이션 래핑). OnboardingRepository 직접 참조 제거.
로그인/인트로/약관 리팩터링
Projects/Presentation/Sources/Login/View/LoginViewController.swift, .../Login/View/IntroViewController.swift, .../Login/View/TermsAgreementViewController.swift, .../Login/View/IntroView.swift
기존 IntroView 삭제, IntroViewController 추가(닉네임 바인딩). LoginViewLoginViewController로 리네임 및 레이아웃·상태 처리·Apple Sign-in 델리게이트 공개화. TermsAgreement 뷰 → TermsAgreementViewController로 리네임 및 레이아웃/문구 변경.
온보딩 흐름·결과 리팩터링
Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift, .../Onboarding/View/OnboardingResultViewController.swift, .../Onboarding/View/OnboardingResultView.swift, .../Onboarding/View/Component/OnboardingChoiceButton.swift, .../Onboarding/View/Component/OnboardingResultSummaryView.swift, .../Onboarding/View/Component/GradientProgressView.swift, .../Onboarding/View/Component/ProgressBarView.swift
OnboardingView → OnboardingViewController로 개편(가시성/라이프사이클 변경). OnboardingResultView 삭제 후 OnboardingResultViewController 추가. 선택 버튼·결과 요약 UI 변경. 기존 GradientProgressView/ProgressBarView 삭제.
뷰모델·DI 변경
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift, .../Login/ViewModel/LoginViewModel.swift, .../Login/ViewModel/IntroViewModel.swift, .../Onboarding/ViewModel/OnboardingViewModel.swift
IntroViewModel 신설(nickname 퍼블리셔). OnboardingViewModel 및 IntroViewModel이 UserDataRepositoryProtocol 주입을 요구하도록 변경(초기화 시 의존성 주입). Presentation DI 등록 업데이트. LoginViewModel 접근제어를 public으로 상향.
네비게이션 바 아키텍처
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift
기존 configureNavigationBar 제거 후 configureCustomNavigationBar(navigationBarStyle:) 도입. NavigationBarStyle 재정의(withBackButton/withTitle/withProgressBar) 및 SnapKit 기반 커스텀 내비게이션 바 구현 추가.
디자인 시스템 및 에셋
Projects/Presentation/Sources/Common/DesignSystem/BitnagilColor.swift, .../BitnagilIcon.swift, .../BitnagilGraphic.swift, .../Common/Component/PrimaryButton.swift, Projects/Presentation/Resources/Images.xcassets/...
번들 참조를 IntroViewController/SplashViewController로 변경. 다수 아이콘·그래픽 추가 및 파일명 변경. 버튼/하이라이트 색상 팔레트 일부 블루→그레이/오렌지로 조정. 에셋(Graphic/Onboarding 등) 대량 추가/개명.
도메인/엔드포인트
Projects/DataSource/Sources/Endpoint/OnboardingEndpoint.swift, Projects/Domain/Sources/Entity/Enum/UserState.swift
OnboardingEndpoint.baseURL을 케이스별(v1/v2)로 분기. UserState에 onboarding = "ONBOARDING" 케이스 추가.
기타 화면 내비/루팅 수정
Projects/Presentation/Sources/Home/View/HomeView.swift, .../EmotionRegister/..., .../RoutineCreation/..., .../Setting/View/SettingView.swift, .../MyPage/View/MypageView.swift, .../ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift
여러 화면에서 기존 시스템 내비바 구성 호출을 제거/주석 처리. 비로그인 루트 변경(이전 Intro → 로그인 루트(LoginViewController)). OnboardingView → OnboardingViewController로 대체 및 결과/문구·레이아웃 조정.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as App(Startup)
  participant Splash as SplashViewController
  participant Scene as SceneDelegate
  participant DI as DIContainer
  participant Nav as UINavigationController
  participant Home as TabBarView

  App->>Splash: show()
  Splash-->>Scene: splashView(_:isCompletedAnimated:)
  Scene->>Scene: resolve UserState
  alt user
    Scene->>Home: set rootViewController = TabBarView
  else onboarding
    Scene->>DI: resolve IntroViewModel
    DI-->>Scene: IntroViewModel
    Scene->>Nav: root = UINavigationController(IntroViewController)
  else guest/nil
    Scene->>DI: resolve LoginViewModel
    DI-->>Scene: LoginViewModel
    Scene->>Nav: root = UINavigationController(LoginViewController)
  end
Loading
sequenceDiagram
  autonumber
  participant Login as LoginViewController
  participant VM as LoginViewModel
  participant Terms as TermsAgreementViewController
  participant Intro as IntroViewController
  participant Home as TabBarView

  Login->>VM: action(signIn)
  VM-->>Login: output.userState
  alt guest
    Login->>Terms: push TermsAgreementViewController
  else onboarding
    Login->>Intro: push IntroViewController
  else user
    Login->>Home: set rootViewController = TabBarView
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • taipaise

Poem

깡충깡충 내가 왔어요, 스플래시가 바뀌었대요 🐇
당근빛 로그인에, 별빛 인트로가 속삭여요 ✨
온보딩 길목에 아이콘들이 줄지어 빛나고 🍊
닉네임 불러오면 다음 길이 환하게 열려요 🌟
코드밭에 점 찍고 춤추며 축하할게요, 깡총! 🐰

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/onboarding-view

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @coderabbitai 요약 to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

🔭 Outside diff range comments (7)
Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift (3)

405-410: detail 경고 라벨 표시/숨김 전환 시 top 오프셋이 복원되지 않는 버그

현재는 경고 라벨이 표시될 때만 repeatRoutineTitleTopConstraint의 오프셋을 업데이트합니다. 이후 라벨이 숨김으로 바뀌면 오프셋을 기본값으로 돌려주지 않아 레이아웃이 계속 어긋난 상태로 남을 수 있습니다. 매 변경 시 계산된 오프셋으로 항상 업데이트하도록 수정해 주세요.

-                self?.detailWarningLabel.isHidden = routines.count < 3
-                if !(self?.detailWarningLabel.isHidden ?? true) {
-                    repeatRoutineTitleLabelTopSpacing += (Layout.warningLabelHeight + Layout.warningLabelTopSpacing)
-                    self?.repeatRoutineTitleTopConstraint?.update(offset: repeatRoutineTitleLabelTopSpacing)
-                }
+                self?.detailWarningLabel.isHidden = routines.count < 3
+                if !(self?.detailWarningLabel.isHidden ?? true) {
+                    repeatRoutineTitleLabelTopSpacing += (Layout.warningLabelHeight + Layout.warningLabelTopSpacing)
+                }
+                self?.repeatRoutineTitleTopConstraint?.update(offset: repeatRoutineTitleLabelTopSpacing)

456-465: 문자열 비교로 All-day 상태 판단 → 도메인 타입 기반으로 전환 권장

executionType == "하루종일"과 같이 문자열로 상태를 판별하면 로컬라이제이션/문구 변경에 취약합니다. ViewModel이 도메인 타입(예: ExecutionType) 또는 불리언(isAllDay)을 직접 방출하도록 바꾸는 편이 견고합니다.

예시 제안:

  • ViewModel 출력: executionTimePublisher: AnyPublisher<ExecutionType, Never> 또는 executionPublisher: AnyPublisher<(title: String, isAllDay: Bool), Never>
  • View: 문자열 비교 제거하고 타입/불리언으로 분기
-            .sink { [weak self] executionType in
-                self?.timePickerButton.configure(title: executionType.description)
-                let allDayButtonImage = executionType == "하루종일"
-                    ? BitnagilIcon.checkedIcon
-                    : BitnagilIcon.uncheckedIcon
-                self?.allDayButton.setImage(allDayButtonImage, for: .normal)
-            }
+            .sink { [weak self] execution in
+                // execution: (title: String, isAllDay: Bool) 형태 또는 ExecutionType(도메인 타입)
+                self?.timePickerButton.configure(title: execution.title)
+                let allDayButtonImage = execution.isAllDay
+                    ? BitnagilIcon.checkedIcon
+                    : BitnagilIcon.uncheckedIcon
+                self?.allDayButton.setImage(allDayButtonImage, for: .normal)
+            }

ViewModel 측 변경 예시는 별도 파일에서 필요하면 제안 드리겠습니다.


535-540: 등록 성공/실패 이벤트 기반 네비게이션으로 이동하도록 수정 필요

현재 RoutineCreationViewModelregisterResultPublisher가 정의되어 있지 않아, 즉시 popViewController 호출 시

  • 비동기 등록 실패 시 에러 노출/재시도 기회가 사라지고
  • 상위 화면에 반영이 지연되어 사용자 체감상 등록이 안 된 것처럼 보일 수 있습니다.

아래와 같이 수정해주세요.

  • ViewModel에 등록 결과를 방출하는 registerResultPublisher 추가

    // ViewModel
    private let registerResultSubject = PassthroughSubject<Result<Void, Error>, Never>()
    var registerResultPublisher: AnyPublisher<Result<Void, Error>, Never> {
        registerResultSubject.eraseToAnyPublisher()
    }
    
    private func registerRoutine() {
        repository.register(parameters)
            .sink { completion in
                switch completion {
                case .finished:
                    self.registerResultSubject.send(.success(()))
                case .failure(let error):
                    self.registerResultSubject.send(.failure(error))
                }
                self.registerResultSubject.send(completion: .finished)
            } receiveValue: { _ in }
            .store(in: &cancellables)
    }
  • RoutineCreationView의 버튼 액션에서 즉시 pop 호출 제거

    - registerButton.addAction(
    -     UIAction { [weak self] _ in
    -         self?.viewModel.action(input: .registerRoutine)
    -         self?.navigationController?.popViewController(animated: true)
    -     },
    -     for: .touchUpInside)
    + registerButton.addAction(
    +     UIAction { [weak self] _ in
    +         self?.viewModel.action(input: .registerRoutine)
    +     },
    +     for: .touchUpInside)
  • bind() 내부에서 registerResultPublisher 구독 후

    • 성공 시에만 popViewController(animated: true)
    • 실패 시 에러 토스트/알럿 표시
    viewModel.registerResultPublisher
        .receive(on: DispatchQueue.main)
        .sink { [weak self] result in
            switch result {
            case .success:
                self?.navigationController?.popViewController(animated: true)
            case .failure(let error):
                self?.showToast(error.localizedDescription)
            }
        }
        .store(in: &cancellables)

위 변경으로 등록 성공/실패 모두 올바르게 처리되도록 해주세요.

Projects/Presentation/Sources/MyPage/View/MypageView.swift (1)

171-173: DIContainer 네임스페이스 불일치 및 치명적 종료(fatalError) 제거 제안

  • 동일 파일 내에서 DI 컨테이너 접근이 Shared.DIContainer(Line 129)와 DIContainer(Lines 171-173)로 혼재되어 있습니다. 모듈 네임스페이스 불일치로 컴파일 에러 또는 다른 컨테이너 참조 위험이 있습니다.
  • 의존성 해제 실패 시 fatalError로 앱이 종료됩니다. 사용자 경로에서 발생 가능한 실패에 대해 크래시 대신 안전한 Early-return + 개발용 assertion으로 처리하는 것을 권장합니다.

아래처럼 수정해 주세요.

-        guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
-            fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
-        }
+        guard let onboardingViewModel = Shared.DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
+            assertionFailure("OnboardingViewModel 의존성이 등록되지 않았습니다.")
+            return
+        }
Projects/Presentation/Sources/Common/Component/PrimaryButton.swift (1)

64-67: Disabled 상태 텍스트 컬러 명시 적용 제안

현재 .disabled일 때도 .normal 상태에만 타이틀 컬러를 설정합니다. 명시적으로 disabled 상태 컬러도 지정해 주면 플랫폼 기본 감쇠에 의존하지 않고 일관된 표현을 보장할 수 있습니다.

     private func updateButtonUI() {
-        setTitleColor(buttonState.buttonTextColor, for: .normal)
+        setTitleColor(buttonState.buttonTextColor, for: .normal)
+        setTitleColor(buttonState.buttonTextColor, for: .disabled)
         backgroundColor = buttonState.buttonColor
     }
Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1)

79-86: 컴파일 오류: Direction.rotation의 switch 구문에 return 누락

switch는 표현식이 아니라 문(statement)라서 각 case에서 명시적으로 값을 반환해야 합니다. 현 상태로는 컴파일되지 않습니다.

아래와 같이 수정해 주세요.

     var rotation: Float {
         switch self {
-        case .up: 90
-        case .down: -90
-        case .left: 0
-        case .right: 180
+        case .up: return 90
+        case .down: return -90
+        case .left: return 0
+        case .right: return 180
         }
     }
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (1)

366-372: 루틴 생성 화면 전환 시 DI 미등록 fatalError 제거 및 안전한 실패 처리 적용 요청

PresentationDependencyAssembler에서 RoutineCreationViewModel이 정상 등록되어 있음을 확인했습니다. 하지만 아래 뷰 전환 지점들에서 DI 실패 시 즉시 앱이 크래시 되므로, 일관된 안전 처리로 대체해주세요.

• Projects/Presentation/Sources/RecommendedRoutine/View/RecommendedRoutineView.swift
– guard·fatalError 사용 구문 (302–304, 326–328)
• Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift
– guard·fatalError 사용 구문 (366–372)
• Projects/Presentation/Sources/Home/View/HomeView.swift
– guard·fatalError 사용 구문 (157–160, 632–635, 648–651)

예시 (ResultRecommendedRoutineView.swift):

-        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 Resolve Fail: 등록되지 않은 의존성입니다.")
+            // TODO: 사용자에게 알림(Alert/Toast 등) 표시 고려
+            return
+        }

위와 동일한 패턴으로 모든 해당 파일의 fatalError 구문을 안전한 로그 + 리턴 처리로 교체해 주세요.

♻️ Duplicate comments (1)
Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)

83-83: 에러 처리 TODO 해결 필요

IntroViewModel과 동일하게 에러 처리가 구현되지 않았습니다.

🧹 Nitpick comments (36)
Projects/Presentation/Resources/Images.xcassets/Onboarding/rounded_rectangle.imageset/Contents.json (2)

1-23: 템플릿 렌더링/벡터 전환 고려(선택)

  • 해당 에셋이 단색/틴트 적용 대상이면 template-rendering-intent를 명시해 틴트 일관성을 확보하는 것을 고려해 주세요.
  • 도형성 리소스라면 PNG 1x/2x/3x 대신 단일 PDF 벡터 + preserves-vector-representation로 전환 시 용량/선명도 측면에서 유리합니다. (현 PNG 유지 시에는 적용 불가)

예시(현 JSON에 properties 블록 추가):

   "info" : {
     "author" : "xcode",
     "version" : 1
-  }
+  },
+  "properties" : {
+    "template-rendering-intent" : "original"
+  }

원하는 경우 "template"로 지정해 시스템/커스텀 틴트를 적용할 수 있습니다.


1-23: 다크 모드 대응 여부 확인(선택)

다크 모드에서 대비가 필요한 배경/도형이라면 luminosity appearance용 다크 변형을 추가해 주세요. 별도 다크 에셋이 있을 경우 각 이미지 엔트리에 appearances를 부여해 분기할 수 있습니다. 필요 시 예시 JSON 패치 제안 드리겠습니다.

Projects/Presentation/Resources/Images.xcassets/Graphic/intro_fomo_graphic.imageset/Contents.json (2)

18-23: 템플릿 틴트 방지를 위해 rendering intent 명시 제안

온보딩 일러스트는 보통 틴트 적용 대상이 아니므로 template-rendering-intent를 명시하면 안전합니다.

아래처럼 properties 블록을 추가해 주세요.

   ],
+  "properties" : {
+    "template-rendering-intent" : "original"
+  },
   "info" : {
     "author" : "xcode",
     "version" : 1
   }

3-17: 다크 모드 전용 이미지가 필요한지 디자인과 확인 권장

인트로/온보딩 그래픽이 다크 모드에서 대비나 색상 표현 이슈가 있다면, luminosity=dark 변형을 추가하는 것을 고려해 주세요. 파일 준비가 되어 있다면 각 scale에 대해 appearances: [{appearance: "luminosity", value: "dark"}] 변형을 추가하면 됩니다.

Projects/Presentation/Resources/Images.xcassets/Onboarding/gray_line.imageset/Contents.json (1)

4-16: 만약 틴트 컬러로 선 색을 제어한다면 template 이미지로 설정 권장

gray_line이 단색 라인으로 사용되며 뷰의 tintColor로 색상을 바꿔야 한다면, imageset에 template-rendering-intent 설정을 추가하는 것이 좋습니다. 이렇게 하면 다크모드/테마에 따라 선 색상을 일관되게 조정할 수 있습니다.

아래 예시처럼 imageset JSON에 properties 섹션을 추가할 수 있습니다(참고용):

{
  "images": [ /* 생략 */ ],
  "info": { "author": "xcode", "version": 1 },
  "properties": {
    "template-rendering-intent": "template"
  }
}
Projects/Presentation/Resources/Images.xcassets/back_button_icon.imageset/Contents.json (1)

1-23: 뒤로가기 아이콘은 템플릿 렌더링 설정을 권장합니다

커스텀 네비게이션 바에서 틴트 컬러로 일관된 색상 적용을 하려면 에셋에 template-rendering-intent 설정을 추가하는 것이 안전합니다. 그렇지 않으면 원본 색이 그대로 표시되어 다크/라이트 테마나 다양한 배경에서 일관성이 깨질 수 있습니다.

아래처럼 properties 블록을 추가해 주세요.

   "info" : {
     "author" : "xcode",
     "version" : 1
-  }
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }
Projects/Presentation/Resources/Images.xcassets/Onboarding/circle_three_icon.imageset/Contents.json (1)

19-23: 아이콘 틴트 사용 시 템플릿 렌더링 의도 지정 권장
해당 리소스가 시스템/테마 색으로 틴트될 아이콘이라면 template-rendering-intent를 추가하세요. 그렇지 않다면 현행 유지해도 무방합니다.

아래와 같이 properties 블록을 추가하면(코드에서 renderingMode(.alwaysTemplate)와 함께) 일관된 틴팅이 가능합니다.

   "info" : {
     "author" : "xcode",
     "version" : 1
-  }
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }
Projects/Presentation/Resources/Images.xcassets/Onboarding/circle_two_icon.imageset/Contents.json (1)

19-23: 아이콘 용도에 따라 템플릿 렌더링 옵션 고려
UI에서 틴트 컬러를 입히는 아이콘이라면 템플릿 렌더링 의도를 명시하는 편이 안전합니다. 필요 시 아래 변경을 반영해 주세요.

   "info" : {
     "author" : "xcode",
     "version" : 1
-  }
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }
Projects/Presentation/Resources/Images.xcassets/Onboarding/progress_step3.imageset/Contents.json (1)

4-17: 다크 모드 전용 아트웍 필요 여부 확인
해당 그래픽이 다크/라이트 모드에서 대비 문제가 있다면, 다크 모드 전용 이미지(appearances: luminosity=dark)를 추가하는 것을 권장합니다.

예시(각 스케일 항목에 다크 변형 추가):

   {
-    "filename" : "progress_step3.png",
+    "filename" : "progress_step3.png",
     "idiom" : "universal",
     "scale" : "1x"
   },
+  {
+    "appearances": [ { "appearance": "luminosity", "value": "dark" } ],
+    "filename" : "progress_step3_dark.png",
+    "idiom" : "universal",
+    "scale" : "1x"
+  },

동일 패턴으로 2x/3x에도 다크 변형을 추가해 주세요.

Projects/Presentation/Resources/Images.xcassets/small_check_icon.imageset/Contents.json (1)

4-17: 스케일별 파일 용량 점검 제안
아이콘은 종종 과도한 해상도/용량으로 커밋됩니다. 빌드 크기 최소화를 위해 각 스케일 PNG가 최적 압축인지, 여백이 과도하지 않은지 확인을 권장합니다.

원하시면 용량 리포트를 자동화하는 스크립트를 제안드리겠습니다.

Projects/Presentation/Resources/Images.xcassets/Onboarding/progress_step4.imageset/Contents.json (1)

4-17: 다크 모드 대응 필요 시 동일하게 변형 추가 고려
progress_step3와 동일한 맥락으로, 다크 모드 대비가 필요하다면 luminosity=dark 변형을 추가해 주세요.

필요 시 예시 JSON 패턴 제공 가능합니다.

Projects/Presentation/Sources/Login/View/IntroViewController.swift (3)

41-48: 불필요한 cancellables 초기화

cancellables가 Line 42에서 빈 배열로 초기화되고 있는데, Line 39에서 이미 프로퍼티 선언 시 초기화되어 있습니다.

 public override init(viewModel: IntroViewModel) {
-    cancellables = []
     super.init(viewModel: viewModel)
 }

58-62: 레이아웃 업데이트 플래그 관리 개선 필요

isLayoutConfigured 플래그를 사용하여 레이아웃 업데이트를 제한하고 있습니다. 하지만 디바이스 회전이나 동적 타입 변경 시 레이아웃 재조정이 필요할 수 있습니다.

디바이스 회전을 지원한다면, trait collection 변경을 감지하여 레이아웃을 업데이트하는 방식을 고려해보세요:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass {
        updateConstraints()
    }
}

82-83: DI 컨테이너 의존성 해결 실패 처리

DIContainer.shared.resolve가 nil을 반환할 때 fatalError를 발생시키고 있습니다. 프로덕션에서는 더 나은 에러 처리가 필요할 수 있습니다.

프로덕션 환경에서는 의존성 해결 실패 시 사용자에게 적절한 에러 메시지를 표시하거나 대체 플로우로 안내하는 것을 고려해보세요.

Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (1)

76-86: 중복 코드 패턴 발견

loadNickname() 메서드가 IntroViewModel과 동일한 패턴으로 구현되어 있습니다. 코드 중복을 줄이기 위해 공통 기능을 추출하는 것을 고려해보세요.

BaseViewModel이나 프로토콜 확장을 통해 닉네임 로드 로직을 공유할 수 있습니다. 리팩토링 구현을 도와드릴까요?

Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)

85-94: Progress 이미지 매핑 확장성 개선

현재 하드코딩된 switch 문으로 progress 이미지를 매핑하고 있습니다. 단계가 추가될 때마다 코드 수정이 필요합니다.

더 확장 가능한 방식으로 개선할 수 있습니다:

 private func progressImage(step: Int) -> UIImage? {
-    switch step {
-    case 1: BitnagilGraphic.progressStep1
-    case 2: BitnagilGraphic.progressStep2
-    case 3: BitnagilGraphic.progressStep3
-    case 4: BitnagilGraphic.progressStep4
-    case 5: BitnagilGraphic.progressStep5
-    default: nil
-    }
+    guard step >= 1 && step <= 5 else { return nil }
+    let imageName = "progressStep\(step)"
+    return UIImage(named: imageName, in: Bundle(for: Self.self), compatibleWith: nil)
 }
Projects/Presentation/Sources/MyPage/View/MypageView.swift (1)

175-181: 온보딩 초기 단계(.time) 재시작 적합성 및 isFromMypage 플래그 동작 검증 필요

MyPage에서 목표 초기화 흐름으로 OnboardingViewController를 .time 스텝부터 띄우는 의도는 합리적입니다. 다만 아래 두 가지를 반드시 확인해주세요:

  • 기획 상 MyPage에서 온보딩을 리셋할 때 첫 진입 스텝이 .time이 맞는지
  • isFromMypage = true 설정 후, 결과 화면(RecommendedRoutine)에서의 등록 및 복귀 로직이 의도대로 실행되는지

참고: OnboardingRecommendedRoutineView는 viewWillAppear에서 매번 registerOnboarding을 호출하도록 구현되어 있어, 뒤로 갔다가 다시 돌아와도 중복 등록 이슈는 발생하지 않습니다.

Projects/Presentation/Sources/Common/Component/PrimaryButton.swift (1)

30-31: Pressed/Disabled 텍스트 컬러 변경은 타당. 대비(contrast)와 다크모드 점검 권장

  • pressed: gray20, disabled: white로 변경은 일관됩니다. 다만 pressed(배경 gray5)와 텍스트(gray20) 대비, disabled(배경 gray96)와 텍스트(white) 대비가 충분한지 QA에서 확인해 주세요.
Projects/Presentation/Sources/Login/View/Component/TermsAgreementItemView.swift (2)

93-94: 동의 상태에 따른 접근성(Accessibility) 개선 제안

시각적 틴트만 변경됩니다. VoiceOver 사용자에게 상태 변화를 전달하기 위해 접근성 속성을 함께 업데이트하는 것을 권장합니다.

     private func updateAttribute() {
         checkButton.tintColor = isAgreed ? BitnagilColor.orange500 : BitnagilColor.gray90
+        isAccessibilityElement = true
+        accessibilityLabel = termType.title
+        accessibilityValue = isAgreed ? "동의함" : "동의 안 함"
+        accessibilityTraits = isAgreed ? [.button, .selected] : [.button]
     }

26-26: 오타: agreementLable → agreementLabel로 변경 권장

변수명 오타는 가독성과 자동완성에 악영향을 줍니다. 전반 교체를 권장합니다.

-    private let agreementLable = UILabel()
+    private let agreementLabel = UILabel()
...
-        agreementLable.attributedText = BitnagilFont(style: .body2, weight: .medium).attributedString(text: termType.title)
+        agreementLabel.attributedText = BitnagilFont(style: .body2, weight: .medium).attributedString(text: termType.title)
...
-        addSubview(agreementLable)
+        addSubview(agreementLabel)
...
-        agreementLable.snp.makeConstraints { make in
+        agreementLabel.snp.makeConstraints { make in

Also applies to: 57-57, 72-72, 81-81

Projects/Presentation/Sources/Login/View/Component/TotalAgreementButton.swift (1)

12-16: Layout 상수 타입 일관화: CFloat → CGFloat

AutoLayout/SnapKit 환경에서는 CGFloat 사용이 표준이며, CFloat 유지 시 불필요한 형 변환이 발생할 수 있습니다.

-        static let checkIconSize: CFloat = 24
+        static let checkIconSize: CGFloat = 24
Projects/Presentation/Sources/Common/Extension/NSAttributedString+.swift (1)

22-25: 하이라이트 속성 적용은 간결해졌으나, 자간/라인하이트 누락 가능성

현재는 .font와 .foregroundColor만 추가되어 BitnagilFont가 제공하는 letterSpacing, lineHeight(ParagraphStyle) 적용이 빠질 수 있습니다. 기본 문자열 생성 시 BitnagilFont의 attributedString 빌더를 활용하면 토큰 일관성이 보장됩니다. 또한 동일 키워드의 다중 등장에 대한 하이라이팅과 빈 문자열 처리도 보완을 권장합니다.

아래처럼 개선해 주세요.

-        let attributedString = NSMutableAttributedString(string: text)
-
-        attributedString.addAttribute(
-            .font,
-            value: BitnagilFont(style: .subtitle1, weight: .medium).font,
-            range: NSRange(location: 0, length: text.count)
-        )
+        // 1) 기본 속성은 Design System 빌더로 생성(라인하이트/커닝 일관성)
+        let base = BitnagilFont(style: .subtitle1, weight: .medium).attributedString(text: text)
+        let attributedString = NSMutableAttributedString(attributedString: base)
 
-        if let range = text.range(of: highlightText) {
-            let nsRange = NSRange(range, in: text)
-            attributedString.addAttributes([
-                .font: BitnagilFont(style: .subtitle1, weight: .semiBold).font,
-                .foregroundColor: BitnagilColor.orange500 ?? .orange
-            ], range: nsRange)
-        }
+        // 2) 빈 키워드 방지 및 전체 구간에서 반복 매칭
+        guard !highlightText.isEmpty else { return attributedString }
+        var searchRange = text.startIndex..<text.endIndex
+        while let range = text.range(of: highlightText, options: [], range: searchRange) {
+            let nsRange = NSRange(range, in: text)
+            attributedString.addAttributes([
+                .font: BitnagilFont(style: .subtitle1, weight: .semiBold).font,
+                .foregroundColor: BitnagilColor.orange500 ?? .orange
+            ], range: nsRange)
+            searchRange = range.upperBound..<text.endIndex
+        }
Projects/Presentation/Sources/Setting/View/SettingView.swift (1)

113-113: 네비게이션 바 설정 주석 처리 — 의도 확인 필요

새 커스텀 내비게이션 바 도입에 따른 비활성화인지, 혹은 실수로 비활성화된 것인지 확인 부탁드립니다. 설정 화면은 일반적으로 Back 버튼/타이틀이 필요합니다.

신규 API를 사용한다면(예: configureCustomNavigationBar), 아래처럼 교체하세요.

-//        configureNavigationBar(navigationStyle: .withBackButton(title: "설정"))
+        configureCustomNavigationBar(style: .withBackButton(title: "설정"))
Projects/Presentation/Sources/Onboarding/Model/OnboardingType.swift (1)

15-17: 온보딩 단계 재정렬(2=feeling, 3=frequency)에 따른 진행바/내비게이션 검증 필요

  • 단계 값 변경으로 ProgressBar(.withProgressBar(step:))와 상단 타이틀/뒤로가기 흐름이 기대 순서와 일치하는지 확인이 필요합니다.
  • 텍스트 개행 제거/문구 변경은 OK. 다만 접근성(멀티라인, Dynamic Type)에서 줄바꿈 제거가 의도한 배치인지 점검 바랍니다.

하드코딩된 step은 관리 비용이 큽니다. 맵으로 관리하면 안전합니다.

예시:

private static let stepMap: [OnboardingType: Int] = [
  .time: 1, .feeling: 2, .frequency: 3, .outdoor: 4
]
var step: Int { Self.stepMap[self] ?? 0 }

Also applies to: 24-26, 34-36, 52-56

Projects/App/Sources/SceneDelegate.swift (2)

57-79: 네트워크 호출을 MainActor에서 수행하지 않도록 이동

@mainactor Task 블록 내부에서 reissueToken()을 호출하고 있어(라인 57~) 메인 스레드에 불필요한 제약을 줄 수 있습니다. 비동기 네트워크는 기본 컨텍스트에서 수행하고, UI 갱신만 메인으로 전환하세요.

-        Task { @MainActor in
-            let userState = await userDataRepository.reissueToken()
-            switch userState {
+        Task {
+            let userState = await userDataRepository.reissueToken()
+            await MainActor.run {
+                switch userState {
                 case .user:
                     window?.rootViewController = TabBarView()
 
                 case .guest, nil:
-                    guard let loginViewModel = DIContainer.shared.resolve(type: LoginViewModel.self)
-                    else { fatalError("loginViewModel 의존성이 등록되지 않았습니다.") }
+                    guard let loginViewModel = DIContainer.shared.resolve(type: LoginViewModel.self) else {
+                        BitnagilLogger.log(logType: .error, message: "LoginViewModel Resolve Fail: 등록되지 않은 의존성입니다.")
+                        return
+                    }
 
                     let loginView = LoginViewController(viewModel: loginViewModel)
                     let navigationController = UINavigationController(rootViewController: loginView)
                     window?.rootViewController = navigationController
 
                 case .onboarding:
-                    guard let introViewModel = DIContainer.shared.resolve(type: IntroViewModel.self)
-                    else { fatalError("introViewModel 의존성이 등록되지 않았습니다.") }
+                    guard let introViewModel = DIContainer.shared.resolve(type: IntroViewModel.self) else {
+                        BitnagilLogger.log(logType: .error, message: "IntroViewModel Resolve Fail: 등록되지 않은 의존성입니다.")
+                        return
+                    }
 
                     let introView = IntroViewController(viewModel: introViewModel)
                     let navigationController = UINavigationController(rootViewController: introView)
                     window?.rootViewController = navigationController
-            }
+                }
+            }
         }

61-78: 루트 전환 유틸리티로 중복 제거 및 전환 애니메이션 권장

각 분기에서 rootViewController 교체 로직이 반복됩니다. 확장 메서드로 통일하고 크로스페이드 전환을 적용하면 UX와 유지보수성이 향상됩니다.

예시(추가 파일/범위에 배치):

import UIKit

extension UIWindow {
    func setRootViewController(_ vc: UIViewController, animated: Bool = true, duration: TimeInterval = 0.25) {
        guard animated else {
            rootViewController = vc
            makeKeyAndVisible()
            return
        }
        let snapshot = snapshotView(afterScreenUpdates: true)
        rootViewController = vc
        makeKeyAndVisible()
        if let snapshot {
            vc.view.addSubview(snapshot)
            UIView.transition(with: vc.view, duration: duration, options: .transitionCrossDissolve, animations: {
                snapshot.alpha = 0
            }, completion: { _ in snapshot.removeFromSuperview() })
        }
    }
}

사용 예:

window?.setRootViewController(navigationController) // 또는 TabBarView()
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (2)

70-72: 불필요한 override 제거: super 호출만 수행합니다

viewWillAppear(_:)가 super만 호출하고 있으므로 삭제가 권장됩니다. SwiftLint 경고(unneeded_override)도 해소됩니다.

다음 변경을 제안합니다.

-    override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-    }

83-88: 회전/윈도 크기 변화 시 top spacing 미반영 가능성

현재 updateMainLabelTopSpacing()는 1회만 실행됩니다(isLayoutConfigured 가드). 기기 회전, 멀티윈도(아이패드) 등 레이아웃 변화 시 간격이 갱신되지 않을 수 있습니다. 회전 미지원이라면 무시 가능하나, 지원 시에는 viewSafeAreaInsetsDidChange() 또는 traitCollectionDidChange(_:)에서 재호출하는 방식을 권장합니다.

원하시면 해당 훅에 최소 변경으로 반영하는 패치를 제안드리겠습니다.

Projects/Presentation/Sources/Onboarding/View/Component/OnboardingResultSummaryView.swift (1)

80-85: 콤마 개수 판별 가독성/견고성 개선 제안

문자 필터링보다는 split을 사용하면 의도(항목 수 판별)가 더 명확합니다. 공백 포함 케이스에도 비교적 견고합니다.

다음과 같이 변경해 보세요.

-        case .feeling:
-            if highlightText.filter({ $0 == "," }).count >= 2 {
+        case .feeling:
+            if highlightText.split(separator: ",").count >= 3 {
                 return "\(highlightText)을"
             } else {
                 return "\(highlightText)을 원하는 중이에요"
             }
Projects/Presentation/Sources/Onboarding/View/Component/OnboardingChoiceButton.swift (2)

24-24: 불필요한 옵셔널 초기화 제거

SwiftLint 경고(redundant_optional_initialization). 옵셔널은 기본값이 nil이므로 초기화 생략이 가능합니다.

다음과 같이 수정하세요.

-    private var subLabel: UILabel? = nil
+    private var subLabel: UILabel?

67-69: 체크 아이콘 렌더링 방식 개선: contentMode 지정

아이콘 비율 유지가 필요한 경우가 많습니다. scaleAspectFit을 지정해 크기 고정 시 왜곡을 방지하세요.

다음 한 줄을 추가하세요.

         checkedIcon.image = BitnagilIcon.orangeCheckedCircleIcon
+        checkedIcon.contentMode = .scaleAspectFit
         checkedIcon.isHidden = true
Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (2)

173-178: 진입점별 네비게이션 바 스타일 분기 누락 가능성

현재 모든 진입점에서 프로그레스 바를 노출합니다. 온보딩 이외(.mypage, .emotion)에서는 진행 단계 개념이 없어 UX 혼선이 생길 수 있습니다. 진입점별로 스타일을 분기하는 방식을 권장합니다.

다음과 같이 수정을 제안합니다.

-        navigationController?.setNavigationBarHidden(true, animated: false)
-        configureCustomNaviagtionBar(navigationBarStyle: .withProgressBar(step: OnboardingType.allCases.count + 1))
+        navigationController?.setNavigationBarHidden(true, animated: false)
+        switch entryPoint {
+        case .onboarding:
+            configureCustomNaviagtionBar(navigationBarStyle: .withProgressBar(step: OnboardingType.allCases.count + 1))
+        case .mypage:
+            configureCustomNaviagtionBar(navigationBarStyle: .withBackButton(title: "추천 루틴"))
+        case .emotion:
+            configureCustomNaviagtionBar(navigationBarStyle: .withBackButton(title: "")) // 혹은 withTitle 필요 시 교체
+        }

116-123: Top spacing 한 번만 업데이트 — 회전 대응 니즈 검토 권장

updateMainLabelTopSpacing()이 최초 1회만 호출되어 회전/윈도 사이즈 변경 시 반영되지 않을 수 있습니다. 온보딩 결과 뷰와 동일 고려사항입니다. 회전 미지원이면 무시 가능, 지원 시에는 적절한 훅에서 재호출 권장합니다.

원하시면 공통 유틸로 뺀 뒤 두 화면에서 재사용하도록 패치 드리겠습니다.

Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift (2)

30-30: 불필요한 nil 초기화 제거

SwiftLint 경고에 따르면 옵셔널 변수를 nil로 초기화하는 것은 불필요합니다.

-    private var subLabel: UILabel? = nil
+    private var subLabel: UILabel?

80-82: 조건문 블록 스타일 개선 제안

한 줄 조건문이라도 일관성을 위해 블록 스타일을 유지하는 것이 좋습니다.

-        if onboarding == .feeling {
-            mainLabel.numberOfLines = 1
-        }
+        mainLabel.numberOfLines = onboarding == .feeling ? 1 : 2
Projects/Presentation/Sources/Login/View/LoginViewController.swift (1)

154-155: 의존성 주입 실패 시 에러 처리 개선 제안

fatalError 대신 더 안전한 에러 처리를 고려해보세요.

-                    guard let introViewModel = DIContainer.shared.resolve(type: IntroViewModel.self)
-                    else { fatalError("introViewModel 의존성이 등록되지 않았습니다.") }
+                    guard let introViewModel = DIContainer.shared.resolve(type: IntroViewModel.self) else {
+                        BitnagilLogger.log(logType: .error, message: "introViewModel 의존성이 등록되지 않았습니다.")
+                        // 에러 알림 표시 또는 기본 화면으로 이동
+                        return
+                    }

Comment on lines +64 to +66
guard let loginViewModel = DIContainer.shared.resolve(type: LoginViewModel.self)
else { fatalError("loginViewModel 의존성이 등록되지 않았습니다.") }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

fatalError 제거 필요(런타임 크래시 위험)

DI 해상 실패 시 fatalError 대신 로깅 후 안전 복귀가 바람직합니다. 위의 리팩터링 diff에 함께 반영되어 있습니다.

Also applies to: 72-74

🤖 Prompt for AI Agents
In Projects/App/Sources/SceneDelegate.swift around lines 64-66 (and similarly
lines 72-74), remove the fatalError usage when DIContainer.shared.resolve(...)
returns nil; instead, log the dependency resolution failure with contextual
information (which dependency failed and scene/session info) and safely return
from the function to avoid a runtime crash; ensure any downstream code that
expects the resolved value is not executed when nil (guard path returns early),
and keep logging asynchronous-safe (use the app logger or os_log) so the app can
continue running gracefully.

Comment on lines +1 to +23
{
"images" : [
{
"filename" : "rounded_rectangle.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "rounded_rectangle@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "rounded_rectangle@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 13, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

리소스 명칭/참조 일치 여부 확인

코드(Be: BitnagilGraphic 등)에서 참조하는 에셋 키("rounded_rectangle")와 실제 imageset 디렉터리명이 일치하는지 확인해 주세요. 오탈자/스네이크 vs 카멜케이스 혼용 시 런타임에서 nil 반환됩니다.

아래 검색으로 참조 위치를 점검할 수 있습니다.


🏁 Script executed:

#!/bin/bash
rg -n "rounded_rectangle" -A 2 -S

Length of output: 1781


onboardingBigRectangle 에셋 참조 불일치 확인 필요

BitnagilGraphic.swift에서 사용된 "rounded_big_rectangle" 이미지가 Assets 카탈로그에 없어 런타임에 nil을 반환합니다.
아래 위치를 확인해 주세요:

  • Assets 디렉토리
    Projects/Presentation/Resources/Images.xcassets/Onboarding/rounded_big_rectangle.imageset (존재하지 않음)
  • 코드
    Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift:20
    static let onboardingBigRectangle = UIImage(named: "rounded_big_rectangle", in: bundle, with: nil)

해결 방안 중 하나를 선택해 적용해 주세요:

  • rounded_big_rectangle.imageset 에셋 추가
  • 코드의 이미지명 "rounded_big_rectangle"을 실제 에셋 키로 변경하여 일치시킴
🤖 Prompt for AI Agents
In
Projects/Presentation/Resources/Images.xcassets/Onboarding/rounded_rectangle.imageset
(lines 1-23) and
Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift around
line 20, the code references "rounded_big_rectangle" but the asset catalog only
contains "rounded_rectangle" variants, causing UIImage(named:) to return nil at
runtime; fix by either adding a new asset set named
rounded_big_rectangle.imageset (with appropriate 1x/2x/3x images and
Contents.json) into the Onboarding asset catalog or change the Swift reference
to the existing asset key (e.g., update the UIImage(named:) call to
"rounded_rectangle") so the asset name matches the catalog.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Image Asset 있을거야 !! rounded_big_rectangle랑 rounded_rectangle 둘 다 !!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what assets actually exist in the Onboarding directory
echo "=== Onboarding Assets Directory Structure ==="
find Projects/Presentation/Resources/Images.xcassets/Onboarding -name "*.imageset" | sort

echo -e "\n=== BitnagilGraphic references to rectangle assets ==="
rg -n "rectangle" Projects/Presentation/Sources/Common/DesignSystem/BitnagilGraphic.swift -A 1 -B 1

echo -e "\n=== All rectangle-related image references in code ==="
rg -n "rounded.*rectangle" -A 1 -B 1

Length of output: 5083


@choijungp 맞습니다! 확인 결과 두 에셋 모두 제대로 존재하고 있네요.

확인된 에셋들:

  • rounded_rectangle.imageset
  • rounded_big_rectangle.imageset

코드 참조도 정확합니다:

  • onboardingRectangle"rounded_rectangle"
  • onboardingBigRectangle"rounded_big_rectangle"

두 에셋 모두 1x, 2x, 3x 해상도로 구성되어 있고 BitnagilGraphic.swift에서 올바르게 참조하고 있습니다. 제가 처음 분석에서 놓친 부분이 있었네요. 혼란을 드려 죄송합니다! 🙏

        /|   /|  
       ( :v:  )
        |(_)|

Comment on lines +1 to +23
{
"images" : [
{
"filename" : "small_check_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "small_check_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "small_check_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

체크 아이콘은 템플릿 렌더링 지정이 바람직
체크마크는 보통 시스템/브랜드 컬러로 틴트되므로 템플릿 렌더링을 명시해 두는 것이 안전합니다.

   "info" : {
     "author" : "xcode",
     "version" : 1
-  }
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
+  }

또한 차후 PNG 대신 단일 PDF 벡터 리소스로 전환하면(해상도별 파일 제거) 자산 관리가 단순화됩니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"images" : [
{
"filename" : "small_check_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "small_check_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "small_check_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "small_check_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "small_check_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "small_check_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
🤖 Prompt for AI Agents
In Projects/Presentation/Resources/Images.xcassets/small_check_icon.imageset
around lines 1-23, the asset JSON doesn’t specify template rendering or vector
settings: add a "properties" object to mark the asset as template-rendered
(e.g., template-rendering-intent: "template") so the checkmark will be tintable
by system/brand colors, and when switching to a single-vector PDF later remove
the scale-specific PNGs and set the vector preservation property (e.g.,
preserves-vector-data: true) in the same properties block to simplify asset
management.

enum BitnagilColor {
private static var bundle: Bundle {
return Bundle(for: IntroView.self)
return Bundle(for: IntroViewController.self)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 13, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

IntroViewController에 의존한 번들 조회는 결합도가 높습니다 — 번들 토큰으로 교체 권장

DesignSystem 계층이 특정 VC 타입(IntroViewController)에 의존하면 모듈 경계가 흐려지고 파일 이동/리팩터링 시 쉽게 깨집니다. 안전하게 자체 번들 토큰 타입을 두고 해당 타입의 번들로 리소스를 로드하세요.

다음과 같이 변경을 제안합니다.

 enum BitnagilColor {
     private static var bundle: Bundle {
-        return Bundle(for: IntroViewController.self)
+        return Bundle(for: PresentationBundleToken.self)
     }

파일 상단(또는 동일 파일 내 enum 위)에 토큰 타입을 추가합니다.

// 번들 토큰: Presentation 모듈 자원 로딩용
private final class PresentationBundleToken {}

Swift Package를 사용 중이라면 더 단순하게 Bundle.module 사용도 가능합니다(패키지 리소스로 구성된 경우).

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Common/DesignSystem/BitnagilColor.swift around
line 12, the code currently obtains a bundle via Bundle(for:
IntroViewController.self) which couples the DesignSystem to a view controller;
replace this with a dedicated bundle token type in the same file (e.g. private
final class PresentationBundleToken {}) and use Bundle(for:
PresentationBundleToken.self) to load resources from the Presentation module, or
if this code is in a Swift Package configured with package resources, use
Bundle.module instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

구조 변경할 때 같이 고려해보겠습니다~

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@taipaise 네, 좋은 접근법이네요! 현재 온보딩 리팩터링과 함께 번들 토큰 분리까지 하면 변경 범위가 너무 커질 수 있으니, 향후 구조 개선 시점에 함께 고려하시는 게 합리적입니다. 👍

enum BitnagilIcon {
private static var bundle: Bundle {
return Bundle(for: IntroView.self)
return Bundle(for: IntroViewController.self)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

리소스 번들 앵커로 IntroViewController를 참조하는 결합도 문제

DesignSystem 계층에서 특정 화면 컨트롤러 타입을 번들 앵커로 참조하면 불필요한 결합이 생깁니다. 독립적인 번들 토큰 타입(또는 SPM이라면 Bundle.module)을 사용해 주세요.

다음과 같이 치환을 권장합니다.

-        return Bundle(for: IntroViewController.self)
+        return Bundle(for: BundleToken.self)

아래와 같이 토큰 타입을 추가해 주세요(파일 내 아무 곳이나 private 스코프로 선언).

private final class BundleToken {}
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift around
line 12, replace the tight coupling that uses IntroViewController as the bundle
anchor with an independent bundle token; add a private final class BundleToken
{} in this file (anywhere at file scope) and change Bundle(for:
IntroViewController.self) to Bundle(for: BundleToken.self) (or use Bundle.module
if this is a SwiftPM package).

Comment on lines +209 to +219
let ifNeedExpandFeelingResult = feelingResult.filter({ $0 == "," }).count >= 2
if ifNeedExpandFeelingResult {
let extendtedFeelingResultView = OnboardingResultSummaryView(onboardingType: nil, highlightText: "")
feelingResultStackView.addArrangedSubview(extendtedFeelingResultView)
extendtedFeelingResultView.snp.makeConstraints { make in
make.height.equalTo(Layout.resultLabelHeight)
}
rectangleImageView.image = BitnagilGraphic.onboardingBigRectangle
rectangleHeightConstraint?.update(offset: Layout.rectangleImageViewMaxHeight)
resultGraphicViewTopConstraint?.update(offset: Layout.resultGraphicViewTopMinSpacing)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

의도치 않은 텍스트 노출(“원하는 중이에요”) 발생 가능성 — spacer view로 대체 권장

확장용 추가 행을 만들 때 OnboardingResultSummaryView(onboardingType: nil, highlightText: "")를 사용하면 기본 문구 “원하는 중이에요”가 표시됩니다. 시각적 공간 확보가 목적이라면 빈 spacer 뷰로 교체해야 합니다.

다음과 같이 수정하세요.

-        if ifNeedExpandFeelingResult {
-            let extendtedFeelingResultView = OnboardingResultSummaryView(onboardingType: nil, highlightText: "")
-            feelingResultStackView.addArrangedSubview(extendtedFeelingResultView)
-            extendtedFeelingResultView.snp.makeConstraints { make in
-                make.height.equalTo(Layout.resultLabelHeight)
-            }
+        if ifNeedExpandFeelingResult {
+            let spacerView = UIView()
+            spacerView.backgroundColor = .clear
+            feelingResultStackView.addArrangedSubview(spacerView)
+            spacerView.snp.makeConstraints { make in
+                make.height.equalTo(Layout.resultLabelHeight)
+            }
             rectangleImageView.image = BitnagilGraphic.onboardingBigRectangle
             rectangleHeightConstraint?.update(offset: Layout.rectangleImageViewMaxHeight)
             resultGraphicViewTopConstraint?.update(offset: Layout.resultGraphicViewTopMinSpacing)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let ifNeedExpandFeelingResult = feelingResult.filter({ $0 == "," }).count >= 2
if ifNeedExpandFeelingResult {
let extendtedFeelingResultView = OnboardingResultSummaryView(onboardingType: nil, highlightText: "")
feelingResultStackView.addArrangedSubview(extendtedFeelingResultView)
extendtedFeelingResultView.snp.makeConstraints { make in
make.height.equalTo(Layout.resultLabelHeight)
}
rectangleImageView.image = BitnagilGraphic.onboardingBigRectangle
rectangleHeightConstraint?.update(offset: Layout.rectangleImageViewMaxHeight)
resultGraphicViewTopConstraint?.update(offset: Layout.resultGraphicViewTopMinSpacing)
}
let ifNeedExpandFeelingResult = feelingResult.filter({ $0 == "," }).count >= 2
if ifNeedExpandFeelingResult {
let spacerView = UIView()
spacerView.backgroundColor = .clear
feelingResultStackView.addArrangedSubview(spacerView)
spacerView.snp.makeConstraints { make in
make.height.equalTo(Layout.resultLabelHeight)
}
rectangleImageView.image = BitnagilGraphic.onboardingBigRectangle
rectangleHeightConstraint?.update(offset: Layout.rectangleImageViewMaxHeight)
resultGraphicViewTopConstraint?.update(offset: Layout.resultGraphicViewTopMinSpacing)
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift
around lines 209–219, creating an OnboardingResultSummaryView(onboardingType:
nil, highlightText: "") for layout spacing causes the default text "원하는 중이에요" to
appear; instead, add a plain spacer UIView (or UIStackView spacer) with the same
height constraint (Layout.resultLabelHeight) to the feelingResultStackView and
apply the same rectangleImageView and constraint updates
(rectangleHeightConstraint?.update and resultGraphicViewTopConstraint?.update)
so the layout behavior is preserved without exposing unintended text.

Comment on lines +235 to +248
private func goToResultRecommendedRoutineView(onboardingChoices: [OnboardingChoiceType]) {
guard let resultRecommendedRoutineViewModel = DIContainer.shared.resolve(type: ResultRecommendedRoutineViewModel.self)
else{ fatalError("resultRecommendedRoutineViewModel 의존성이 등록되지 않았습니다.") }

var resultRecommendedView: ResultRecommendedRoutineView
if isFromMypage {
resultRecommendedRoutineViewModel.configure(viewModelType: .mypage(onboardingChoices: onboardingChoices))
resultRecommendedView = ResultRecommendedRoutineView(entryPoint: .mypage, viewModel: resultRecommendedRoutineViewModel)
} else {
resultRecommendedRoutineViewModel.configure(viewModelType: .onboarding(onboardingChoices: onboardingChoices))
resultRecommendedView = ResultRecommendedRoutineView(entryPoint: .onboarding, viewModel: resultRecommendedRoutineViewModel)
}
self.navigationController?.pushViewController(resultRecommendedView, animated: true)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

DI 미등록 시 앱 크래시(fatalError) → 안전한 실패 처리로 교체

의존성 해상도 실패 시 fatalError는 사용자 단에서 크래시를 유발합니다. 로깅 후 조용히 반환하거나 사용자에게 알림을 표시하는 방식으로 바꾸는 것이 바람직합니다.

DI 등록 여부도 함께 점검 부탁드립니다(아래 스크립트 참고).

-        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 Resolve Fail: 등록되지 않은 의존성입니다.")
+            // TODO: 사용자에게 에러를 안내하는 Alert/Toast 표시 고려
+            return
+        }

아래 스크립트로 DI 등록을 빠르게 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# DI 등록 여부 점검
set -eu

echo "[검색] ResultRecommendedRoutineViewModel 등록/해상도"
rg -n 'register\(.+ResultRecommendedRoutineViewModel\.self|resolve\(type:\s*ResultRecommendedRoutineViewModel\.self' -A 3

echo "[검색] OnboardingViewModel 등록/해상도"
rg -n 'register\(.+OnboardingViewModel\.self|resolve\(type:\s*OnboardingViewModel\.self' -A 3

echo "[검색] RoutineCreationViewModel 등록/해상도"
rg -n 'register\(.+RoutineCreationViewModel\.self|resolve\(type:\s*RoutineCreationViewModel\.self' -A 3

Length of output: 8890


앱 크래시 방지를 위해 fatalError → 안전한 실패 처리로 교체 제안

DIContainer에 ResultRecommendedRoutineViewModel, OnboardingViewModel, RoutineCreationViewModel가 모두 올바르게 등록된 것이 확인되었습니다.
하지만 의존성 해상도 실패 시 fatalError가 호출되면 운영 중인 앱이 즉시 크래시되므로, 아래처럼 로그 기록 후 조용히 복귀하거나 사용자 알림을 띄우는 방식으로 변경할 것을 권장드립니다.

예시 패치 (OnboardingResultViewController.swift):

-        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 Resolve Fail: 등록되지 않은 의존성입니다.")
+            // TODO: 알림(Alert/Toast) 표시 후 return 처리
+            return
+        }

• 동일한 fatalError(resolve:) 패턴이 아래 파일들에도 사용되고 있으니, 전역적으로 변경을 고려해 주세요.

  • EmotionRegisterView.swift
  • ResultRecommendedRoutineView.swift
  • RecommendedRoutineView.swift
  • HomeView.swift 등
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift
around lines 235–248, the DIContainer.resolve(...) call uses fatalError on
failure which crashes the app; replace this with safe failure handling by
checking the optional, logging the resolution error (using the app logger or
os_log), presenting a user-friendly alert (on main thread) or silently
returning/navigating back, and ensuring no further work occurs when the view
model is missing; apply the same non-crashing pattern to other files mentioned
(EmotionRegisterView.swift, ResultRecommendedRoutineView.swift,
RecommendedRoutineView.swift, HomeView.swift).

Comment on lines +111 to 112
// configureNavigationBar(navigationStyle: .withBackButton(title: navigationTitle))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

새 네비게이션 바 API 교체 누락

구(舊) API 호출이 주석 처리된 뒤 신(新) API 호출이 추가되지 않아, 화면 진입 시 커스텀 네비게이션 바가 표시되지 않을 가능성이 큽니다. 본 PR의 네비게이션 바 리팩토링 의도에 맞춰 교체가 필요합니다.

아래와 같이 교체해 주세요. 새 확장 메서드명이 실제로 configureCustomNaviagtionBar(오탈자 포함)인지, configureCustomNavigationBar인지 코드베이스의 정의와 일치하는지 반드시 확인 바랍니다.

-//        configureNavigationBar(navigationStyle: .withBackButton(title: navigationTitle))
+configureCustomNaviagtionBar(navigationBarStyle: .withBackButton(title: navigationTitle))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// configureNavigationBar(navigationStyle: .withBackButton(title: navigationTitle))
}
configureCustomNaviagtionBar(navigationBarStyle: .withBackButton(title: navigationTitle))
}
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/RoutineCreation/View/RoutineCreationView.swift
around lines 111-112, the old navigation bar call was commented out but no new
API call was added; replace the commented line with a call to the new navigation
bar setup (e.g. configureCustomNavigationBar(navigationTitle) or the exact
signature used in the codebase), making sure to use the exact method
name/spelling defined in the project (verify whether it is
configureCustomNaviagtionBar or configureCustomNavigationBar) and pass the
appropriate parameters (navigationTitle or equivalent) so the custom navigation
bar is shown on view entry.

Comment on lines +212 to 217
guard let loginViewModel = DIContainer.shared.resolve(type: LoginViewModel.self)
else { fatalError("loginViewModel 의존성이 등록되지 않았습니다.") }

let loginView = LoginViewController(viewModel: loginViewModel)
let navigationController = UINavigationController(rootViewController: loginView)
window?.rootViewController = navigationController
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

DI 해상 실패 시 fatalError로 앱 크래시 유발

런타임 의존성 누락은 출시 빌드에서 앱 크래시로 이어집니다. 로깅 후 안전하게 복귀하거나 사용자에게 오류 안내를 표시하는 쪽이 적절합니다.

아래와 같이 수정을 권장합니다.

-                    guard let loginViewModel = DIContainer.shared.resolve(type: LoginViewModel.self)
-                    else { fatalError("loginViewModel 의존성이 등록되지 않았습니다.") }
+                    guard let loginViewModel = DIContainer.shared.resolve(type: LoginViewModel.self) else {
+                        BitnagilLogger.log(logType: .error, message: "LoginViewModel Resolve Fail: 등록되지 않은 의존성입니다.")
+                        return
+                    }

추가로, 루트 전환 로직은 공통화하여 재사용/애니메이션 처리(크로스페이드 등)를 권장합니다.

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Setting/View/SettingView.swift around lines
212-217, remove the fatalError on DI resolution failure and instead log the
missing dependency, present a safe fallback (e.g., an error/placeholder view
controller or an onboarding/login flow) to the user, and return early without
crashing; also refactor the rootViewController switch into a shared helper that
accepts a view controller and performs the transition with optional animation
(crossfade) so this root-change logic is centralized and reusable.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)

19-19: 메서드명 오타 수정 필요

configureCustomNaviagtionBar에 오타가 있습니다. Navigation이 올바른 철자입니다.

-func configureCustomNavigationBar(navigationBarStyle: NavigationBarStyle) {
+func configureCustomNavigationBar(navigationBarStyle: NavigationBarStyle) {
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (3)

139-144: 겹침 표현을 위해 offset으로 변경 권장

이전 리뷰에서 논의된 바와 같이, rectangleImageView가 resultGraphicView와 겹치도록 하려는 의도라면 inset 대신 offset에 음수 값을 사용하는 것이 의미상 더 적절합니다.

-make.top.equalTo(resultGraphicView.snp.bottom).inset(Layout.rectangleImageViewTopInset)
+make.top.equalTo(resultGraphicView.snp.bottom).offset(-Layout.rectangleImageViewTopInset)

209-219: 의도치 않은 텍스트 노출 방지를 위한 spacer view 사용 권장

확장용 추가 행에서 OnboardingResultSummaryView(onboardingType: nil, highlightText: "")를 사용하면 기본 문구가 표시될 수 있습니다. 순수한 spacer view로 교체하는 것이 바람직합니다.

-if ifNeedExpandFeelingResult {
-    let extendtedFeelingResultView = OnboardingResultSummaryView(onboardingType: nil, highlightText: "")
-    feelingResultStackView.addArrangedSubview(extendtedFeelingResultView)
-    extendtedFeelingResultView.snp.makeConstraints { make in
-        make.height.equalTo(Layout.resultLabelHeight)
-    }
+if ifNeedExpandFeelingResult {
+    let spacerView = UIView()
+    spacerView.backgroundColor = .clear
+    feelingResultStackView.addArrangedSubview(spacerView)
+    spacerView.snp.makeConstraints { make in
+        make.height.equalTo(Layout.resultLabelHeight)
+    }
     rectangleImageView.image = BitnagilGraphic.onboardingBigRectangle
     rectangleHeightConstraint?.update(offset: Layout.rectangleImageViewMaxHeight)
     resultGraphicViewTopConstraint?.update(offset: Layout.resultGraphicViewTopMinSpacing)
 }

235-248: 앱 크래시 방지를 위해 안전한 실패 처리로 교체

DIContainer 해상도 실패 시 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 Resolve Fail: 등록되지 않은 의존성입니다.")
+    // TODO: 사용자에게 에러를 안내하는 Alert/Toast 표시 고려
+    return
+}
🧹 Nitpick comments (3)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)

85-94: progressImage 메서드 최적화 제안

현재 구현은 정확하지만, 더 간결하게 작성할 수 있습니다.

-private func progressImage(step: Int) -> UIImage? {
-    switch step {
-    case 1: BitnagilGraphic.progressStep1
-    case 2: BitnagilGraphic.progressStep2
-    case 3: BitnagilGraphic.progressStep3
-    case 4: BitnagilGraphic.progressStep4
-    case 5: BitnagilGraphic.progressStep5
-    default: nil
-    }
-}
+private func progressImage(step: Int) -> UIImage? {
+    guard step >= 1 && step <= 5 else { return nil }
+    let imageName = "progressStep\(step)"
+    return BitnagilGraphic.value(forKey: imageName) as? UIImage
+}
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (1)

70-72: 불필요한 override 메서드 제거

SwiftLint 경고에 따르면 viewWillAppear에서 super 호출 외에 다른 작업이 없으므로 메서드 자체를 제거해야 합니다.

-override func viewWillAppear(_ animated: Bool) {
-    super.viewWillAppear(animated)
-}
Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift (1)

30-30: 불필요한 nil 초기화 제거

SwiftLint 경고에 따르면 optional 변수를 nil로 초기화하는 것은 중복됩니다.

-private var subLabel: UILabel? = nil
+private var subLabel: UILabel?
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a433fc and c439dd0.

📒 Files selected for processing (5)
  • Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1 hunks)
  • Projects/Presentation/Sources/Login/View/TermsAgreementViewController.swift (7 hunks)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (1 hunks)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift (8 hunks)
  • Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.swift (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Projects/Presentation/Sources/Login/View/TermsAgreementViewController.swift
  • Projects/Presentation/Sources/ResultRecommendedRoutine/View/ResultRecommendedRoutineView.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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
📚 Learning: 2025-08-13T10:59:57.399Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#46
File: Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift:139-144
Timestamp: 2025-08-13T10:59:57.399Z
Learning: OnboardingResultViewController에서 rectangleImageView가 resultGraphicView와 약간 겹치도록 의도된 레이아웃임. 겹침을 위해서는 inset 대신 offset에 음수 값을 사용하는 것이 더 의미상 적절함.

Applied to files:

  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift
📚 Learning: 2025-07-16T09:21:15.038Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.

Applied to files:

  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift
  • Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift
🧬 Code Graph Analysis (1)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (6)
Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (3)
  • action (57-74)
  • loadNickname (77-86)
  • makeOnboardingResult (170-185)
Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift (3)
  • updateMainLabelTopSpacing (70-74)
  • configureAttribute (76-120)
  • configureLayout (122-163)
Projects/Presentation/Sources/Onboarding/View/Component/OnboardingResultSummaryView.swift (2)
  • configureAttribute (33-49)
  • configureLayout (51-73)
Projects/Presentation/Sources/Common/DesignSystem/Font/BitnagilFont.swift (1)
  • attributedString (50-66)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)
  • configureCustomNavigationBar (19-28)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • resolve (18-25)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift

[Warning] 70-70: Remove overridden functions that don't do anything except call their super

(unneeded_override)

Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift

[Warning] 30-30: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

⏰ 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 (4)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)

36-41: 커스텀 네비게이션 바 구현이 잘 설계되었습니다.

백버튼 액션 처리와 아이콘 설정이 적절하며, weak self를 사용한 메모리 관리도 올바릅니다.

Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift (3)

13-13: 클래스명 변경 및 구조 개선이 잘 반영되었습니다.

OnboardingView에서 OnboardingViewController로의 변경과 final class 적용이 적절합니다. MVC 패턴에 더 부합하는 네이밍입니다.


124-126: 새로운 커스텀 네비게이션 바 적용이 일관성 있게 처리되었습니다.

시스템 네비게이션 바를 숨기고 커스텀 네비게이션 바를 사용하는 접근 방식이 일관성 있게 적용되었습니다. 진행 상태 표시도 적절합니다.


228-242: 온보딩 플로우 순서 변경이 올바르게 반영되었습니다.

feeling → frequency로의 순서 변경과 최종 단계에서 OnboardingResultViewController로의 라우팅이 PR 목표에 맞게 잘 구현되었습니다.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (3)

139-144: 의도된 겹침 레이아웃을 offset(음수)로 적용한 점 아주 좋습니다

inset → offset(-spacing)로 교체해 겹침 의도를 명확히 표현했습니다. 스냅킷 의미상도 적절합니다.


209-219: 의도치 않은 기본 문구 노출 가능성 — spacer 뷰로 교체

빈 레이아웃 확장을 위해 OnboardingResultSummaryView(onboardingType: nil, highlightText: "")를 사용하면 기본 문구(“원하는 중이에요”)가 표시될 수 있습니다. 빈 UIView spacer로 대체하세요.

-        let ifNeedExpandFeelingResult = feelingResult.filter({ $0 == "," }).count >= 2
-        if ifNeedExpandFeelingResult {
-            let extendtedFeelingResultView = OnboardingResultSummaryView(onboardingType: nil, highlightText: "")
-            feelingResultStackView.addArrangedSubview(extendtedFeelingResultView)
-            extendtedFeelingResultView.snp.makeConstraints { make in
-                make.height.equalTo(Layout.resultLabelHeight)
-            }
-            rectangleImageView.image = BitnagilGraphic.onboardingBigRectangle
-            rectangleHeightConstraint?.update(offset: Layout.rectangleImageViewMaxHeight)
-            resultGraphicViewTopConstraint?.update(offset: Layout.resultGraphicViewTopMinSpacing)
-        }
+        let ifNeedExpandFeelingResult = feelingResult.filter({ $0 == "," }).count >= 2
+        if ifNeedExpandFeelingResult {
+            let spacerView = UIView()
+            spacerView.backgroundColor = .clear
+            feelingResultStackView.addArrangedSubview(spacerView)
+            spacerView.snp.makeConstraints { make in
+                make.height.equalTo(Layout.resultLabelHeight)
+            }
+            rectangleImageView.image = BitnagilGraphic.onboardingBigRectangle
+            rectangleHeightConstraint?.update(offset: Layout.rectangleImageViewMaxHeight)
+            resultGraphicViewTopConstraint?.update(offset: Layout.resultGraphicViewTopMinSpacing)
+        }

235-248: fatalError로 인한 런타임 크래시 제거: 안전한 실패 처리로 변경

DI 해상도 실패 시 fatalError는 운영 중 크래시를 유발합니다. DIContainer.resolve가 이미 로그를 남기므로, 여기서는 조용히 반환하거나 사용자 안내(UI)로 대체하세요.

-        guard let resultRecommendedRoutineViewModel = DIContainer.shared.resolve(type: ResultRecommendedRoutineViewModel.self)
-        else{ fatalError("resultRecommendedRoutineViewModel 의존성이 등록되지 않았습니다.") }
+        guard let resultRecommendedRoutineViewModel = DIContainer.shared.resolve(type: ResultRecommendedRoutineViewModel.self) else {
+            // DIContainer.resolve가 이미 에러 로그를 남깁니다. 필요 시 사용자 안내(UI)를 추가하세요.
+            return
+        }
🧹 Nitpick comments (4)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (4)

70-72: 불필요한 오버라이드 제거 권장 (SwiftLint: unneeded_override)

super만 호출하는 viewWillAppear는 삭제하세요. 유지할 이유가 없고 린트 경고를 유발합니다.

-    override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-    }

126-130: mainLabel trailing 제약 추가로 일관된 좌우 마진 보장

다른 화면들과 일관된 좌우 마진 유지 및 긴 텍스트 줄바꿈 여지를 위해 trailing 제약을 추가하는 것이 좋습니다.

         mainLabel.snp.makeConstraints { make in
             make.leading.equalTo(safeArea).offset(Layout.horizontalMargin)
+            make.trailing.equalTo(safeArea).inset(Layout.horizontalMargin)
             mainLabelTopConstraint = make.top.equalTo(safeArea).offset(Layout.mainLabelMinTopSpacing).constraint
             make.height.equalTo(Layout.mainLabelHeight)
         }

100-103: 닉네임 로딩 전 어색한 플레이스홀더 문구 노출 방지

초기 “님은 ...”은 잠깐 보였다 사라지는 어색함을 줄 수 있습니다. 초기엔 nameLabel을 숨기고 닉네임 수신 시 표시하세요.

-        nameLabel.text = "님은 ..."
+        nameLabel.isHidden = true
         nameLabel.font = BitnagilFont(style: .title3, weight: .semiBold).font
         nameLabel.textColor = BitnagilColor.gray10

167-171: 닉네임 수신 시 라벨 표시 전환

위 변경과 맞물려, 닉네임을 받으면 라벨을 보여주도록 처리합니다.

-            .sink { [weak self] nickname in
-                self?.nameLabel.text = "\(nickname)님은 ..."
-            }
+            .sink { [weak self] nickname in
+                self?.nameLabel.text = "\(nickname)님은 ..."
+                self?.nameLabel.isHidden = false
+            }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c439dd0 and 6723c97.

📒 Files selected for processing (1)
  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (1 hunks)
🧰 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 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
📚 Learning: 2025-08-13T10:59:57.399Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#46
File: Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift:139-144
Timestamp: 2025-08-13T10:59:57.399Z
Learning: OnboardingResultViewController에서 rectangleImageView가 resultGraphicView와 약간 겹치도록 의도된 레이아웃임. 겹침을 위해서는 inset 대신 offset에 음수 값을 사용하는 것이 더 의미상 적절함.

Applied to files:

  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift
📚 Learning: 2025-07-16T09:21:15.038Z
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.

Applied to files:

  • Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift
🧬 Code Graph Analysis (1)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift (6)
Projects/Presentation/Sources/Onboarding/ViewModel/OnboardingViewModel.swift (3)
  • action (57-74)
  • loadNickname (77-86)
  • makeOnboardingResult (170-185)
Projects/Presentation/Sources/Onboarding/View/OnboardingViewController.swift (6)
  • viewWillAppear (56-59)
  • viewDidLayoutSubviews (61-68)
  • updateMainLabelTopSpacing (70-74)
  • configureAttribute (76-120)
  • configureLayout (122-163)
  • bind (165-200)
Projects/Presentation/Sources/Onboarding/View/Component/OnboardingResultSummaryView.swift (2)
  • configureAttribute (33-49)
  • configureLayout (51-73)
Projects/Presentation/Sources/Common/DesignSystem/Font/BitnagilFont.swift (1)
  • attributedString (50-66)
Projects/Presentation/Sources/Common/Extension/UIViewController+.swift (1)
  • configureCustomNavigationBar (19-28)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
  • resolve (18-25)
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift

[Warning] 70-70: Remove overridden functions that don't do anything except call their super

(unneeded_override)

Comment on lines +195 to +197
resultStackView.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

UIStackView arrangedSubviews 제거 방식 수정 필요

arrangedSubviews는 removeArrangedSubview(_:)+removeFromSuperview()로 제거해야 합니다. 현재처럼 바로 removeFromSuperview()만 호출하면 스택 뷰의 관리 목록에는 남아 레이아웃 경고/비용이 생길 수 있습니다.

-        resultStackView.arrangedSubviews.forEach {
-            $0.removeFromSuperview()
-        }
+        resultStackView.arrangedSubviews.forEach { subview in
+            resultStackView.removeArrangedSubview(subview)
+            subview.removeFromSuperview()
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
resultStackView.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
resultStackView.arrangedSubviews.forEach { subview in
resultStackView.removeArrangedSubview(subview)
subview.removeFromSuperview()
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Onboarding/View/OnboardingResultViewController.swift
around lines 195 to 197, the code removes stack view children by calling
removeFromSuperview() directly which leaves them listed in
UIStackView.arrangedSubviews and can cause layout warnings; update the loop to
for each arranged subview call resultStackView.removeArrangedSubview(view)
followed by view.removeFromSuperview() so the view is removed from the stack’s
arrangedSubviews array and then from the view hierarchy.

Copy link
Copy Markdown
Collaborator

@taipaise taipaise left a comment

Choose a reason for hiding this comment

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

리뷰 노트 확인했습니다~! 백버튼 사용 시 참고하겠습니다.
루틴의 경우, 루틴 정보를 보여주는 DTO 자체의 구조는 유지되는 것으로 이해했는데, 혹시 제가 잘못이해했을까요? 만약 맞다면 현재는 long 타입으로 내려오지만, 서버 분들이 id를 스트링 값으로 수정해주시면 정상적으로 표시되지 않을까.. 합니다.

디자인이 대격변하면서 작업량이 매우 많아졌네요 ㅜㅜ 오늘도 고생하셨습니다!! 완고!!! 어푸루브~!

{
"images" : [
{
"filename" : "circle_three_icon.png",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

아하 여담이지만, 1x의 경우도 뒤에 이름이 @1x로 붙는줄 알았는데 필수가 아니었군요.. 배우고 갑니다

enum BitnagilColor {
private static var bundle: Bundle {
return Bundle(for: IntroView.self)
return Bundle(for: IntroViewController.self)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

구조 변경할 때 같이 고려해보겠습니다~


private func updateConstraints() {
let height = view.bounds.height
if height <= 667 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요 667이라는 값은, 노치 영역 + 하단 바 영역을 포함한 기기의 화면 높이일까요?

해당 값이 어떻게 나오게 되었는지 궁금합니다!
만약에 기기 화면의 높이가 중요한 것이라면, safeArea의 top이 SuperView로부터 0 이상 떨어져있는지 아닌지 여부로도 확인해볼 수 있을 것 같습니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

사실 지피띠니가 알려준 SE 3세대의 전체 화면 높이입니다 ..
SE인 경우 화면 레이아웃을 다시 계산해야 하는 경우가 많아서 667 기준으로만 하긴 했어유 ㅠ.ㅠ

@choijungp choijungp merged commit 93662e4 into develop Aug 14, 2025
2 checks passed
@choijungp choijungp deleted the feat/onboarding-view branch August 14, 2025 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants