Skip to content

위치 기능 추가 #802

Merged
Do-hyun-Kim merged 4 commits into
developfrom
feat/#add-location
Apr 14, 2026
Merged

위치 기능 추가 #802
Do-hyun-Kim merged 4 commits into
developfrom
feat/#add-location

Conversation

@akrudal

@akrudal akrudal commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 위치 검색/선택 화면 추가
    • 포스트 및 추억에서 위치 정보 노출
    • 스튜디오 테마 목록 조회 및 테마 선택 기능 추가
  • 개선사항

    • 위치 관련 UI/뷰 개선 (이미지 위 위치 표시 등)
    • AI 관련 스튜디오 항목 조회 기능 추가
  • 버그 픽스 / 변경

    • 스튜디오 업로드 제한 시간 적용 (00:00–09:59)
  • 기타

    • 앱 버전 업데이트 (1.5.0)

@akrudal akrudal requested a review from Do-hyun-Kim April 14, 2026 14:27
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 797dea92-6355-46fd-bd9f-286a0f8b14bc

📥 Commits

Reviewing files that changed from the base of the PR and between a4d3cdd and 261546c.

📒 Files selected for processing (2)
  • 14th-team5-iOS/Bibbi/Project.swift
  • Tuist/ProjectDescriptionHelpers/Project+Templates.swift
✅ Files skipped from review due to trivial changes (1)
  • Tuist/ProjectDescriptionHelpers/Project+Templates.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • 14th-team5-iOS/Bibbi/Project.swift

Walkthrough

앱 버전이 1.4.5에서 1.5.0으로 상향되고, 위치 선택 화면과 관련 데이터 흐름(권한, Kakao Local 연동, 선택 콜백)이 추가되었으며, 스튜디오 테마 리스트 기능(엔티티·API·뷰·네비게이션)과 포스트/캘린더에 위치 필드가 도입되었습니다.

Changes

Cohort / File(s) Summary
버전·빌드설정
14th-team5-iOS/Bibbi/Project.swift, Tuist/ProjectDescriptionHelpers/Project+Templates.swift
앱 마케팅 버전 1.4.5 → 1.5.0. Info.plist 빌드변수 기반 KAKAO/GOOGLE API 키 항목 및 위치 사용 설명 추가.
위치 기능 전체
.../Presentation/Location/ViewControllers/LocationViewController.swift, .../Presentation/Location/Model/LocationPlace.swift
새 Location 화면 추가: Kakao Local API 클라이언트, 권한/위치 업데이트, 검색·카테고 병렬 조회, 결과 정렬·중복제거, 선택 콜백 제공.
카메라 -> 위치 통합
.../Presentation/Camera/CameraDisplayViewReactor.swift, .../Presentation/Camera/CameraDisplayViewController.swift, .../Application/Navigator/CameraDisplayNavigator.swift
카메라 화면에서 위치 선택 플로우 추가: 네비게이터에 onSelect 콜백 전달, Reactor 상태에 위도/경도/주소 저장 및 포스트 생성 요청에 포함.
포스트/엔티티·DTO 확장
.../Domain/.../PostEntity.swift, .../Domain/.../CreatePostRequest.swift, .../Data/.../CreatePostRequestDTO.swift, .../Data/.../PostListResponseDTO.swift, .../Data/.../ArrayResponseDailyCalendarResponseDTO.swift, .../Domain/.../ArrayResponseDailyCalendarEntity.swift
Post 및 Calendar 관련 도메인/DTO에 latitude, longitude, address 필드 추가 및 매핑 반영.
스튜디오 기능(테마 목록)
.../Domain/.../StudioThemeEntity.swift, .../Data/.../AIGroupListResponseDTO.swift, .../Domain/.../UseCases/FetchStudioThemeUseCase.swift, .../Domain/.../Repositories/StudioRepository.swift, .../Data/.../Repositories/PostRepository.swift
새로운 StudioThemeEntity 도입, AI 이미지 타입 DTO·매핑 추가, 테마 목록 조회 UseCase/Repository 메서드 추가.
스튜디오 페이지 리팩토링
.../Presentation/Home/Reactor/pages/StudioPageReactor.swift, .../Presentation/Home/ViewControllers/pages/StudioPageViewController.swift, .../Presentation/Home/Model/StudioSection.swift
스튜디오 페이지가 배너 기반에서 테이블(목록) 기반으로 변경. 액션/뮤테이션/상태가 테마 목록 중심으로 재구성.
스튜디오 네비게이션·래퍼·셀
.../Application/Navigator/StudioPageNavigator.swift, .../Application/Navigator/Wrapper/Studio/StudioWrapper.swift, .../Presentation/Studio/ViewControllers/StudioThemeTableViewCell.swift, .../Presentation/Home/Views/Cell/StudioThemeTableViewCell2.swift
toStudio(theme:) 시그니처 변경 및 Wrapper에 theme 주입, 테마 목록 셀들 추가/수정.
UI 위치 표시 추가
.../Presentation/PostDetail/Views/PostDetailCollectionViewCell.swift, .../Presentation/Calendar/View/Cell/MemoriesCalendarPostCell.swift, .../Presentation/Calendar/View/MemoriesCalendarPostImageView.swift
이미지 상단에 위치 컨테이너·아이콘·라벨 추가, 주소 표시 토글 로직 및 레이아웃/스타일 추가.
AI/포스트 API 확장
.../Data/Sources/APIs/Post/PostAPIs.swift, .../Data/.../PostListAPIWorker.swift
AI 관련 엔드포인트 확장: AI 이미지 타입 목록 조회, AI 포스트 생성, fetchAICount에 aiPostType 인자 추가; createPost 스펙에 body 반영.
DI·UseCase 등록 및 호출 변경
.../Application/DIContainer/StudioDIContainer.swift, .../Domain/.../UseCases/Studio/FetchStudioCountUseCase.swift
FetchStudioThemeListUseCase 등록 추가. FetchStudioCountUseCase/Repository/UseCase 호출에 aiPostType 인자 전달로 시그니처 변경.
기타 변경·정리
.../Presentation/Home/Reactor/MainViewReactor.swift, .../Presentation/Home/Views/Cell/MainPostCollectionViewCell.swift, .../Core/.../BibbiSegmentedControl.swift, .../Core/.../BBNetwork/API.swift, .../Data/.../Repositories/AppRepository.swift
카메라 버튼 페이지 인덱스 검사 명확화, 코드 포맷 정리, segmented control tint/background 수정, X-APP-KEY 값 갱신, appKey 상수 교체 등 소폭 변경.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Camera as CameraDisplayViewController
    participant Reactor as CameraDisplayViewReactor
    participant Navigator as CameraDisplayNavigator
    participant Location as LocationViewController
    participant Kakao as KakaoLocalAPIClient

    User->>Camera: 위치 추가 버튼 탭
    Camera->>Reactor: Action.didTapLocationButton
    Reactor->>Navigator: toLocationSearch(onSelect:)
    Navigator->>Location: present LocationViewController

    User->>Location: 검색어 입력 / 근처 목록 확인
    Location->>Kakao: keyword/category search 요청
    Kakao-->>Location: 검색 결과 반환
    Location->>User: 결과 표시
    User->>Location: 항목 선택
    Location-->>Navigator: onSelectLocation(lat,lng,address)
    Navigator->>Reactor: didSelectLocation(...) 전달
    Reactor->>Reactor: Mutation.setLocation(...) 업데이트
    Reactor->>Camera: locationButtonTitle 갱신
    User->>Camera: 포스트 생성 요청
    Reactor->>API: CreatePostRequest (image, content, latitude, longitude, address)
Loading
sequenceDiagram
    participant User
    participant StudioPage as StudioPageViewController
    participant StudioReactor as StudioPageReactor
    participant UseCase as FetchStudioThemeListUseCase
    participant Repo as PostRepository
    participant API as PostAPI

    User->>StudioPage: 화면 진입 (viewWillAppear)
    StudioPage->>StudioReactor: Action.fetchThemeList
    StudioReactor->>UseCase: execute()
    UseCase->>Repo: fetchStudioThemeList()
    Repo->>API: fetchAIImageTypes()
    API-->>Repo: DTO 목록 반환
    Repo-->>UseCase: [StudioThemeEntity]
    UseCase-->>StudioReactor: [StudioThemeEntity]
    StudioReactor->>StudioPage: 상태 업데이트 (studioSection)
    User->>StudioPage: 테마 선택
    StudioPage->>StudioReactor: Action.didSelectTheme(theme)
    StudioReactor->>Navigator: toStudio(theme:)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 새 지도를 들고 뛰어왔어요, 발자국마다 좌표를 남기며.
카카오에 속삭이고, 테마와 손잡고,
테이블 위에 추억을 펼쳐요.
버전 1.5.0로 한 뽀짝 도약! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 '위치 기능 추가'는 변경사항의 핵심인 위치 선택 기능을 정확하고 명확하게 요약하고 있습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#add-location

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (17)
14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/MainViewReactor.swift (1)

255-268: cameraButtonTap 분기를 정수 비교 대신 타입 기반 분기로 고정하는 것을 권장합니다.

현재처럼 pageIndex를 직접 비교하면 탭 인덱스 변경/추가 시 무반응 케이스가 조용히 생기기 쉽습니다. BibbiFeedType.init(index:)로 변환 후 switch 분기하면 안전성이 더 높습니다.

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

In `@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/MainViewReactor.swift`
around lines 255 - 268, Replace the integer-based branching in the
cameraButtonTap handler with a type-based switch: create a BibbiFeedType from
currentState.pageIndex using BibbiFeedType.init(index:) (e.g., let feedType =
BibbiFeedType(index: currentState.pageIndex)) and switch on feedType; for the
case that previously corresponded to index 0 replicate the logic using
currentState.isInTime / currentState.isCameraDisabled to call
pushViewController(type: .photoUploadDisabledAlert) or pushViewController(type:
.cameraViewController(.survival)), and for the case that previously corresponded
to index 1 use currentState.isMeSurvivalUploadedToday to call
pushViewController(type: .cameraViewController(.mission)) or
pushViewController(type: .survivalAlert); include a default case that does
nothing (or a safe fallback) so new indexes don’t silently misbehave.
14th-team5-iOS/Domain/Sources/Entities/PostList/CreatePostRequest.swift (1)

23-32: 좌표 범위(위도/경도) 1차 검증을 도메인에서 처리하는 것을 권장합니다.

현재는 임의 Double 값이 그대로 전달됩니다. 잘못된 좌표가 섞이면 서버/지도 연동 오류 원인 추적이 어려워집니다.

♻️ 제안 코드
     ) {
         self.imageUrl = imageUrl
         self.content = content
         self.uploadTime = uploadTime
-        self.latitude = latitude
-        self.longitude = longitude
+        self.latitude = latitude.flatMap { (-90.0...90.0).contains($0) ? $0 : nil }
+        self.longitude = longitude.flatMap { (-180.0...180.0).contains($0) ? $0 : nil }
         self.address = address
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@14th-team5-iOS/Domain/Sources/Entities/PostList/CreatePostRequest.swift`
around lines 23 - 32, The CreatePostRequest initializer currently assigns
latitude/longitude directly; add domain-level validation inside the initializer
(CreatePostRequest init) to ensure latitude is in -90...90 and longitude is in
-180...180, and handle invalid values by failing early (make the init failable,
throw a DomainError like InvalidCoordinates, or use precondition with a clear
message) instead of accepting arbitrary Doubles; update property assignment to
only set latitude/longitude when they pass the range checks and include a clear
error/handling path (e.g., InvalidCoordinates) so callers and logs can identify
bad input.
14th-team5-iOS/Bibbi/Sources/Presentation/PostDetail/Views/PostDetailCollectionViewCell.swift (1)

230-241: 주소 표시 로직은 메서드로 추출해 중복/가독성을 줄이는 편이 좋습니다.

현재 subscribe 내부 분기 로직을 별도 setLocation(address:) 형태로 분리하면 유지보수가 더 쉬워집니다.

♻️ 제안 코드
 reactor.state.map { $0.post.address }
     .distinctUntilChanged()
     .withUnretained(self)
-    .subscribe { owner, address in
-        if let address, !address.isEmpty {
-            owner.locationLabel.text = address
-            owner.locationContainerView.isHidden = false
-        } else {
-            owner.locationContainerView.isHidden = true
-        }
-    }
+    .subscribe { owner, address in
+        owner.setLocation(address: address)
+    }
     .disposed(by: disposeBag)
private extension PostDetailCollectionViewCell {
    func setLocation(address: String?) {
        if let address, !address.isEmpty {
            locationLabel.text = address
            locationContainerView.isHidden = false
        } else {
            locationContainerView.isHidden = true
        }
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/PostDetail/Views/PostDetailCollectionViewCell.swift`
around lines 230 - 241, Extract the address handling logic in
PostDetailCollectionViewCell's reactor.state subscription into a private method
(e.g., add a private extension method setLocation(address: String?)) and replace
the inline subscribe block body with a call to that method; ensure
setLocation(address:) implements the same behavior (if let address,
!address.isEmpty -> set locationLabel.text and show locationContainerView, else
hide locationContainerView) and keep the subscription unchanged except for
calling self.setLocation(address: address).
14th-team5-iOS/Data/Sources/APIs/Post/PostAPIs.swift (1)

18-24: type 문자열은 enum으로 제한하는 것을 권장합니다.

Line 53, Line 62처럼 raw String을 그대로 쓰면 잘못된 값이 런타임까지 넘어갈 수 있습니다. 컴파일 타임 제약을 두는 편이 안전합니다.

Also applies to: 53-69

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

In `@14th-team5-iOS/Data/Sources/APIs/Post/PostAPIs.swift` around lines 18 - 24,
The PostAPIs cases use raw String for the `type` parameter which allows invalid
values at runtime; introduce a dedicated enum (e.g., PostType or AIImageType)
with explicit cases for allowed types and replace the String associated values
on PostAPIs cases fetchAICount, createPost, and createAIPost to use that enum
instead, update the enum to conform to RawRepresentable/CustomStringConvertible
or provide a computed property to emit the string when building the request, and
then update all call sites and the request-building logic to use the new enum
value rather than raw strings so compilation enforces valid types.
14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioThemeTableViewCell.swift (1)

8-133: 파일 전체 주석 코드는 정리하는 게 좋습니다.

현재 상태는 빌드 산출물엔 영향이 없지만, Line 8~Line 133 전체가 dead/commented code라서 기능 의도를 혼동시키고 유지보수 비용을 올립니다. 실제 사용 예정이면 구현을 활성화하고, 아니면 파일 제거를 권장합니다.

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

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioThemeTableViewCell.swift`
around lines 8 - 133, This file contains an entire commented-out implementation
of StudioThemeTableViewCell (including commented imports and methods like
bind(reactor:), setupUI(), setupAutoLayout(), setupAttributes(), bindInput(_:),
bindOutput(_:), setCell(_:), createContentDataSource(), and
UICollectionViewDelegateFlowLayout conformance) which should be cleaned up:
either restore/uncomment the real implementation if it's intended to be used
(ensure imports and the StudioThemeTableViewCell class and its methods are
active) or remove the file and any dead commented blocks if it’s not needed;
specifically delete or uncomment the large block between the commented import
lines and the end of the class so references like StudioThemeTableViewCell,
setCell, bindInput, bindOutput, and createContentDataSource no longer exist only
as comments.
14th-team5-iOS/Bibbi/Sources/Presentation/Calendar/View/Cell/MemoriesCalendarPostCell.swift (1)

88-91: 주소만 감시하도록 스트림을 좁히면 불필요한 UI 업데이트를 줄일 수 있습니다.

현재는 dailyPost 전체 변경 시 재실행되므로, address만 map해서 distinctUntilChanged() 하는 방식이 더 효율적입니다.

♻️ 제안 코드
-dailyPost
-    .distinctUntilChanged()
-    .drive(with: self, onNext: { $0.postImageView.setLocation(address: $1.address) })
+dailyPost
+    .map(\.address)
+    .distinctUntilChanged()
+    .drive(with: self, onNext: { $0.postImageView.setLocation(address: $1) })
     .disposed(by: disposeBag)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Calendar/View/Cell/MemoriesCalendarPostCell.swift`
around lines 88 - 91, The stream currently observes the entire dailyPost and
triggers UI updates for any change; narrow it to only the address to avoid
unnecessary updates by mapping dailyPost to its address, applying
distinctUntilChanged() on that address, and then driving
postImageView.setLocation(address:) with the resulting observable (use
dailyPost.map { $0.address }.distinctUntilChanged() before drive). Ensure you
reference the existing dailyPost observable, the distinctUntilChanged call, and
the postImageView.setLocation(address:) sink when making the change.
14th-team5-iOS/Bibbi/Sources/Presentation/Home/ViewControllers/pages/StudioPageViewController.swift (1)

77-88: 주석 처리된 코드 정리 필요

bannerView.rx.count 바인딩 관련 주석 처리된 코드가 남아있습니다. 더 이상 사용하지 않는다면 제거하는 것이 좋습니다.

♻️ 제안된 수정
         reactor.pulse(\.$studioSection)
             .observe(on: MainScheduler.instance)
             .map(Array.init(with:))
             .bind(to: tableView.rx.items(dataSource: createDataSource()))
             .disposed(by: disposeBag)
-        
-//        reactor.state
-//            .map { $0.memoriesCount }
-//            .distinctUntilChanged()
-//            .bind(to: bannerView.rx.count)
-//            .disposed(by: disposeBag)
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/ViewControllers/pages/StudioPageViewController.swift`
around lines 77 - 88, Remove the dead/commented binding for bannerView (the
reactor.state -> .map { $0.memoriesCount } -> .distinctUntilChanged() ->
.bind(to: bannerView.rx.count) chain) from StudioPageViewController; if the
banner is truly unused also remove any related unused outlets/properties (e.g.,
bannerView) and imports, otherwise restore the binding properly to reactor.state
and bannerView.rx.count. Ensure you update disposeBag usage and keep the active
reactor.pulse(...) binding intact.
14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/pages/StudioPageReactor.swift (2)

38-47: 주석 처리된 transform 메서드 정리

StudioGlobalState의 이벤트를 처리하던 transform 메서드가 주석 처리되어 있습니다. 더 이상 필요하지 않다면 제거해 주세요.

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

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/pages/StudioPageReactor.swift`
around lines 38 - 47, The commented-out transform method in StudioPageReactor is
dead code; either restore its functionality or remove it: if it's no longer
needed, delete the commented block that references transform,
provider.studioGlobalState.event, receiveMemoriesCount and .setMemoriesCount to
clean up the file; if it should remain, re-enable and ensure
StudioPageReactor.transform subscribes to provider.studioGlobalState.event and
maps .receiveMemoriesCount(count) to .setMemoriesCount(count) before merging
with the incoming mutation Observable.

25-28: 사용되지 않는 isError 프로퍼티

isError가 선언되어 있지만, mutatereduce에서 설정되거나 사용되지 않습니다. 에러 상태를 UI에 표시할 계획이라면 구현을 추가하고, 그렇지 않다면 제거하는 것이 좋습니다.

💡 에러 처리 구현 예시
 enum Mutation {
     case updateStudioThemeDataSource([StudioThemeEntity])
+    case setError(Bool)
 }

 // mutate에서:
 case .fetchThemeList:
     return studioPostUseCase.execute()
         .flatMap { list -> Observable<Mutation> in
             guard let list else {
-                return Observable.from([])
+                return .just(.setError(true))
             }
             return Observable.from([
                 .updateStudioThemeDataSource(list)
             ])
         }
-        .catch { _ in .empty() }
+        .catch { _ in .just(.setError(true)) }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/pages/StudioPageReactor.swift`
around lines 25 - 28, The State struct currently declares an unused isError
Bool; either remove isError from State in StudioPageReactor or wire it into your
error handling by setting it in mutate and returning it in reduce: when
asynchronous actions (e.g., loadStudioSection or network fetches handled in
mutate) fail, emit a mutation that carries an error flag and update
State.isError in reduce so the UI can react; otherwise delete isError to avoid
dead state.
14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioBannerView.swift (1)

155-160: formatDate 함수 중복

StudioThemeTableViewCell2.swiftformatStudioDate 메서드와 동일한 로직이 중복됩니다. 공통 유틸리티로 추출하면 유지보수성이 향상됩니다.

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

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioBannerView.swift`
around lines 155 - 160, The date-formatting logic in StudioBannerView.formatDate
duplicates StudioThemeTableViewCell2.formatStudioDate; extract this into a
shared utility (e.g., a new static helper function or String/Date extension
named formatStudioDate or formatToMonthDay) and replace both
StudioBannerView.formatDate and StudioThemeTableViewCell2.formatStudioDate to
call that common utility; ensure the helper validates the "YYYY-MM-DD" input
(splitting on "-" and returning original on failure) and update both callers to
use the new shared function to remove duplication.
14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift (6)

507-511: 위치 업데이트 타임아웃 미설정

startUpdatingLocation() 호출 후 위치를 받지 못하는 경우(예: 실내, GPS 신호 약함) 무기한 대기할 수 있습니다. 일정 시간 후 타임아웃 처리를 추가하면 사용자 경험이 개선됩니다.

💡 타임아웃 처리 예시
func startUpdatingLocation() {
    locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
    locationManager.startUpdatingLocation()
    loadingIndicator.startAnimating()
    
    // 10초 후 위치를 받지 못하면 타임아웃 처리
    DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
        guard let self, self.currentLocation == nil else { return }
        self.locationManager.stopUpdatingLocation()
        self.loadingIndicator.stopAnimating()
        self.updateEmptyState(message: "위치를 확인하는 데 시간이 걸려요. 검색을 이용해 주세요.")
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift`
around lines 507 - 511, The startUpdatingLocation() method currently begins
location updates and the loadingIndicator but never times out; add a timeout
fallback so if locationManager does not deliver a location within a set interval
(e.g., 10s) you stop updates, stop the loadingIndicator and show an empty/error
state; implement this by scheduling a DispatchQueue.main.asyncAfter deadline
inside startUpdatingLocation() that checks self.currentLocation (or similar
state) and if still nil calls locationManager.stopUpdatingLocation(),
loadingIndicator.stopAnimating(), and updateEmptyState(message: ...) to inform
the user.

63-96: URL 생성 시 강제 언래핑 제거 권장

Line 64에서 URLComponents 초기화에 강제 언래핑(!)을 사용하고 있습니다. URL 문자열이 고정값이라 실패 가능성은 낮지만, 안전한 옵셔널 처리가 더 방어적인 코딩입니다.

또한, 네트워크 요청에 타임아웃이 설정되어 있지 않아 네트워크 불안정 시 요청이 무기한 대기할 수 있습니다.

♻️ 제안된 수정
     func searchKeyword(
         query: String,
         x: Double? = nil,
         y: Double? = nil,
         radius: Int = 20000,
         completion: `@escaping` ([KakaoDocument]) -> Void
     ) -> URLSessionDataTask? {
-        var components = URLComponents(string: "https://dapi.kakao.com/v2/local/search/keyword.json")!
+        guard var components = URLComponents(string: "https://dapi.kakao.com/v2/local/search/keyword.json") else {
+            completion([])
+            return nil
+        }
         var queryItems: [URLQueryItem] = [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift`
around lines 63 - 96, Replace the forced unwrap of URLComponents by safely
initializing it and bailing with completion([]) if it fails (replace
URLComponents(string: "https://dapi.kakao.com/v2/local/search/keyword.json")!
with a guard let components = URLComponents(string: ...) else { completion([]);
return nil }) and keep populating components.queryItems as before; also set a
request timeout by using URLRequest(url: url, timeoutInterval:
<reasonableSeconds>) (the existing variable request created in this function) so
the URLSessionDataTask from this method respects a timeout. Reference: the
components variable created from URLComponents(...) and the URLRequest(url: url)
used before creating the data task.

1-47: 파일 분리 고려

단일 파일에 여러 타입(LocationPlace, KakaoLocalResponse, KakaoDocument, KakaoLocalAPIClient, LocationViewReactor, LocationTableViewCell, LocationViewController)이 정의되어 있어 약 800줄에 달합니다.

private 접근 제한으로 캡슐화되어 있지만, 파일이 커지면 유지보수성이 떨어질 수 있습니다. 특히 KakaoLocalAPIClient는 다른 화면에서도 재사용될 가능성이 있어 별도 파일로 분리하는 것을 고려해 보세요.

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

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift`
around lines 1 - 47, This file groups many types which reduces maintainability;
split out each logical type into its own file: move LocationPlace,
KakaoLocalResponse and KakaoDocument (models) into a Models file, extract
KakaoLocalAPIClient into its own KakaoLocalAPIClient.swift (make it
internal/public if it should be reused outside this module), and move
LocationViewReactor and LocationTableViewCell into their own files
(LocationViewReactor.swift, LocationTableViewCell.swift) while leaving
LocationViewController.swift focused on view logic; ensure you update access
levels (remove private where cross-file reuse is required), add necessary
imports (CoreLocation/DesignSystem/ReactorKit/RxSwift) in each new file, and run
the build to fix any visibility/import issues.

138-171: Reactor 상태 관리 범위 고려

현재 LocationViewReactor는 검색 쿼리만 관리하고, nearbyPlaces, searchPlaces, isSearching 등의 상태는 ViewController에서 직접 관리합니다. ReactorKit 패턴에서는 이러한 상태들도 Reactor에서 관리하는 것이 일반적입니다.

현재 구현도 동작하지만, 향후 테스트 용이성과 상태 관리 일관성을 위해 리팩토링을 고려해 볼 수 있습니다.

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

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift`
around lines 138 - 171, LocationViewReactor currently only holds query
(State.query) while other UI state (nearbyPlaces, searchPlaces, isSearching) is
managed in the ViewController; move those into the reactor by adding properties
to State (e.g., nearbyPlaces: [Place], searchPlaces: [Place], isSearching:
Bool), extend Action (e.g., startSearch, setNearbyPlaces, setSearchResults,
setSearching) and Mutation (e.g., setNearbyPlaces, setSearchResults,
setSearching) and implement the logic in mutate and reduce to emit and apply
those mutations (update mutate to handle new Action cases and reduce to update
State accordingly), then update the LocationViewController bindings to observe
LocationViewReactor.state for UI updates instead of holding those values
locally.

52-54: API 키 누락 시 조기 감지

restAPIKey가 누락되면 빈 문자열을 반환하여 API 요청이 인증 오류로 실패합니다. 개발 중 문제를 조기에 발견하기 위해 디버그 빌드에서 경고를 출력하거나 assertion을 추가하는 것이 좋습니다.

💡 제안 예시
     private var restAPIKey: String {
-        Bundle.main.object(forInfoDictionaryKey: "KAKAO_LOCAL_REST_API_KEY") as? String ?? ""
+        guard let key = Bundle.main.object(forInfoDictionaryKey: "KAKAO_LOCAL_REST_API_KEY") as? String, !key.isEmpty else {
+            `#if` DEBUG
+            assertionFailure("KAKAO_LOCAL_REST_API_KEY is missing from Info.plist")
+            `#endif`
+            return ""
+        }
+        return key
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift`
around lines 52 - 54, The computed property restAPIKey in LocationViewController
returns an empty string when the KAKAO_LOCAL_REST_API_KEY is missing; update it
to fail fast in development by checking the Bundle value and asserting/logging
when empty: replace the silent fallback with a debug-only assertion or
precondition failure and a clear process/log message that references restAPIKey
(and LocationViewController) so missing API key issues are surfaced early while
still allowing a safe empty fallback or graceful handling in release builds.

98-133: searchCategory도 동일한 강제 언래핑 사용

searchKeyword와 마찬가지로 Line 106에서 URLComponents 강제 언래핑이 있습니다. 동일한 패턴으로 수정해 주세요.

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

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift`
around lines 98 - 133, The searchCategory function currently force-unwraps
URLComponents(string: ...) — replace that force-unwrapping with safe unwrapping:
create components using guard let components = URLComponents(string:
"https://dapi.kakao.com/v2/local/search/category.json") else { completion([]);
return nil } (or otherwise handle the failure) before assigning queryItems and
proceeding; update the function (searchCategory) to bail out cleanly when
components is nil rather than crashing.
14th-team5-iOS/Data/Sources/APIs/Post/PostListAPIWorker.swift (1)

80-91: 문서 주석 업데이트 필요

createAIPost 메서드의 문서 주석이 createPost에서 복사되어 동일합니다. AI 포스트 생성의 특성을 반영하도록 주석을 업데이트해 주세요.

📝 제안된 수정
-    /// 게시물 생성을 하기 위한 Method 입니다.
+    /// AI 게시물 생성을 하기 위한 Method 입니다.
     /// HTTP Method : POST
     /// - Parameters :
     ///     - query : CreatePostQuery
     ///     - body : CreatePostRequestDTO
-    ///         - type: String
     /// - Returns : CreatePostResponseDTO
     func createAIPost(query: CreatePostQuery, body: CreatePostRequestDTO) -> Observable<CreatePostResponseDTO?> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@14th-team5-iOS/Data/Sources/APIs/Post/PostListAPIWorker.swift` around lines
80 - 91, The doc comment above func createAIPost(query: CreatePostQuery, body:
CreatePostRequestDTO) -> Observable<CreatePostResponseDTO?> is a copy of
createPost and should be updated to describe AI post creation: change the
summary line to indicate "AI 게시물 생성" (or similar), keep HTTP Method : POST, list
parameters with their concrete types (query: CreatePostQuery — include that
query.type represents the AI post type, body: CreatePostRequestDTO — note any
AI-specific fields expected), and the return type CreatePostResponseDTO; ensure
the comment is accurate and concise and replaces the old createPost wording so
readers understand this method creates AI-generated posts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Calendar/View/MemoriesCalendarPostImageView.swift`:
- Around line 117-123: In setLocation(address: String?) update the validity
check to trim whitespace before testing emptiness so that addresses like "   "
are treated as empty; if the trimmed address is non-empty set locationLabel.text
= trimmedAddress and show locationContainerView, otherwise hide
locationContainerView — adjust references in this function (setLocation,
locationLabel, locationContainerView) accordingly.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/Cell/MainPostCollectionViewCell.swift`:
- Around line 96-101: The image loading branch in MainPostCollectionViewCell
stops the activity indicator only on successful URL creation/Kingfisher load
path, but the else branch that assigns the fallback image (imageView.image =
DesignSystemAsset.emptyCaseGraphicEmoji.image) never calls
indicator.stopAnimating(), leaving the spinner visible; update the else branch
(or refactor so indicator.stopAnimating() is always executed after attempting to
set the image) to call indicator.stopAnimating() when data.imageURL is not a
valid URL so the spinner is always stopped regardless of URL validity.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/Cell/StudioThemeTableViewCell2.swift`:
- Around line 123-136: The cell's tooltip visibility can persist across reused
cells because didTapInfo() only toggles state; ensure toolTipView has a
deterministic initial state and is reset on reuse by (1) setting
toolTipView.isHidden = true when configuring/initializing the view (where
toolTipView.do { ... } is used) and (2) overriding prepareForReuse() in
StudioThemeTableViewCell2 to explicitly set toolTipView.isHidden = true (and
call super.prepareForReuse()), leaving didTapInfo() as the toggle handler.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioViewController.swift`:
- Around line 114-116: The UI currently captures
StudioReactor.isInThemePeriod(theme:) once and sets uploadButton.isHidden
statically, causing stale UI across date boundaries; change the logic to
recompute the period check at each relevant event (e.g., in viewWillAppear, on
reactor state updates, or via a daily timer) and update uploadButton
visibility/style accordingly. Locate the one-time call to
StudioReactor.isInThemePeriod(theme: reactor.currentState.theme) and replace it
with a reactive or event-driven update path that re-evaluates
StudioReactor.isInThemePeriod using reactor.currentState.theme whenever the view
appears or the theme/state changes (also apply same change for the similar check
at lines 126-127), and ensure the update modifies uploadButton.isHidden (and any
style properties) each time.

In `@14th-team5-iOS/Core/Sources/Trash/BBNetwork/API.swift`:
- Line 56: The .xAppKey case in API.swift currently returns a hardcoded string
which duplicates keys across the codebase (e.g., AppRepository, Bundle+Ext);
replace that hardcoded value so all callers read the single source of truth (use
the existing Bundle+Ext public var xAppKey or a centralized
AppRepository.xAppKey constant) by changing the .xAppKey return to reference
that property instead of the literal string and remove other duplicated literals
to ensure all paths use the same symbol.

---

Nitpick comments:
In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Calendar/View/Cell/MemoriesCalendarPostCell.swift`:
- Around line 88-91: The stream currently observes the entire dailyPost and
triggers UI updates for any change; narrow it to only the address to avoid
unnecessary updates by mapping dailyPost to its address, applying
distinctUntilChanged() on that address, and then driving
postImageView.setLocation(address:) with the resulting observable (use
dailyPost.map { $0.address }.distinctUntilChanged() before drive). Ensure you
reference the existing dailyPost observable, the distinctUntilChanged call, and
the postImageView.setLocation(address:) sink when making the change.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/MainViewReactor.swift`:
- Around line 255-268: Replace the integer-based branching in the
cameraButtonTap handler with a type-based switch: create a BibbiFeedType from
currentState.pageIndex using BibbiFeedType.init(index:) (e.g., let feedType =
BibbiFeedType(index: currentState.pageIndex)) and switch on feedType; for the
case that previously corresponded to index 0 replicate the logic using
currentState.isInTime / currentState.isCameraDisabled to call
pushViewController(type: .photoUploadDisabledAlert) or pushViewController(type:
.cameraViewController(.survival)), and for the case that previously corresponded
to index 1 use currentState.isMeSurvivalUploadedToday to call
pushViewController(type: .cameraViewController(.mission)) or
pushViewController(type: .survivalAlert); include a default case that does
nothing (or a safe fallback) so new indexes don’t silently misbehave.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/pages/StudioPageReactor.swift`:
- Around line 38-47: The commented-out transform method in StudioPageReactor is
dead code; either restore its functionality or remove it: if it's no longer
needed, delete the commented block that references transform,
provider.studioGlobalState.event, receiveMemoriesCount and .setMemoriesCount to
clean up the file; if it should remain, re-enable and ensure
StudioPageReactor.transform subscribes to provider.studioGlobalState.event and
maps .receiveMemoriesCount(count) to .setMemoriesCount(count) before merging
with the incoming mutation Observable.
- Around line 25-28: The State struct currently declares an unused isError Bool;
either remove isError from State in StudioPageReactor or wire it into your error
handling by setting it in mutate and returning it in reduce: when asynchronous
actions (e.g., loadStudioSection or network fetches handled in mutate) fail,
emit a mutation that carries an error flag and update State.isError in reduce so
the UI can react; otherwise delete isError to avoid dead state.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/ViewControllers/pages/StudioPageViewController.swift`:
- Around line 77-88: Remove the dead/commented binding for bannerView (the
reactor.state -> .map { $0.memoriesCount } -> .distinctUntilChanged() ->
.bind(to: bannerView.rx.count) chain) from StudioPageViewController; if the
banner is truly unused also remove any related unused outlets/properties (e.g.,
bannerView) and imports, otherwise restore the binding properly to reactor.state
and bannerView.rx.count. Ensure you update disposeBag usage and keep the active
reactor.pulse(...) binding intact.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift`:
- Around line 507-511: The startUpdatingLocation() method currently begins
location updates and the loadingIndicator but never times out; add a timeout
fallback so if locationManager does not deliver a location within a set interval
(e.g., 10s) you stop updates, stop the loadingIndicator and show an empty/error
state; implement this by scheduling a DispatchQueue.main.asyncAfter deadline
inside startUpdatingLocation() that checks self.currentLocation (or similar
state) and if still nil calls locationManager.stopUpdatingLocation(),
loadingIndicator.stopAnimating(), and updateEmptyState(message: ...) to inform
the user.
- Around line 63-96: Replace the forced unwrap of URLComponents by safely
initializing it and bailing with completion([]) if it fails (replace
URLComponents(string: "https://dapi.kakao.com/v2/local/search/keyword.json")!
with a guard let components = URLComponents(string: ...) else { completion([]);
return nil }) and keep populating components.queryItems as before; also set a
request timeout by using URLRequest(url: url, timeoutInterval:
<reasonableSeconds>) (the existing variable request created in this function) so
the URLSessionDataTask from this method respects a timeout. Reference: the
components variable created from URLComponents(...) and the URLRequest(url: url)
used before creating the data task.
- Around line 1-47: This file groups many types which reduces maintainability;
split out each logical type into its own file: move LocationPlace,
KakaoLocalResponse and KakaoDocument (models) into a Models file, extract
KakaoLocalAPIClient into its own KakaoLocalAPIClient.swift (make it
internal/public if it should be reused outside this module), and move
LocationViewReactor and LocationTableViewCell into their own files
(LocationViewReactor.swift, LocationTableViewCell.swift) while leaving
LocationViewController.swift focused on view logic; ensure you update access
levels (remove private where cross-file reuse is required), add necessary
imports (CoreLocation/DesignSystem/ReactorKit/RxSwift) in each new file, and run
the build to fix any visibility/import issues.
- Around line 138-171: LocationViewReactor currently only holds query
(State.query) while other UI state (nearbyPlaces, searchPlaces, isSearching) is
managed in the ViewController; move those into the reactor by adding properties
to State (e.g., nearbyPlaces: [Place], searchPlaces: [Place], isSearching:
Bool), extend Action (e.g., startSearch, setNearbyPlaces, setSearchResults,
setSearching) and Mutation (e.g., setNearbyPlaces, setSearchResults,
setSearching) and implement the logic in mutate and reduce to emit and apply
those mutations (update mutate to handle new Action cases and reduce to update
State accordingly), then update the LocationViewController bindings to observe
LocationViewReactor.state for UI updates instead of holding those values
locally.
- Around line 52-54: The computed property restAPIKey in LocationViewController
returns an empty string when the KAKAO_LOCAL_REST_API_KEY is missing; update it
to fail fast in development by checking the Bundle value and asserting/logging
when empty: replace the silent fallback with a debug-only assertion or
precondition failure and a clear process/log message that references restAPIKey
(and LocationViewController) so missing API key issues are surfaced early while
still allowing a safe empty fallback or graceful handling in release builds.
- Around line 98-133: The searchCategory function currently force-unwraps
URLComponents(string: ...) — replace that force-unwrapping with safe unwrapping:
create components using guard let components = URLComponents(string:
"https://dapi.kakao.com/v2/local/search/category.json") else { completion([]);
return nil } (or otherwise handle the failure) before assigning queryItems and
proceeding; update the function (searchCategory) to bail out cleanly when
components is nil rather than crashing.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/PostDetail/Views/PostDetailCollectionViewCell.swift`:
- Around line 230-241: Extract the address handling logic in
PostDetailCollectionViewCell's reactor.state subscription into a private method
(e.g., add a private extension method setLocation(address: String?)) and replace
the inline subscribe block body with a call to that method; ensure
setLocation(address:) implements the same behavior (if let address,
!address.isEmpty -> set locationLabel.text and show locationContainerView, else
hide locationContainerView) and keep the subscription unchanged except for
calling self.setLocation(address: address).

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioBannerView.swift`:
- Around line 155-160: The date-formatting logic in StudioBannerView.formatDate
duplicates StudioThemeTableViewCell2.formatStudioDate; extract this into a
shared utility (e.g., a new static helper function or String/Date extension
named formatStudioDate or formatToMonthDay) and replace both
StudioBannerView.formatDate and StudioThemeTableViewCell2.formatStudioDate to
call that common utility; ensure the helper validates the "YYYY-MM-DD" input
(splitting on "-" and returning original on failure) and update both callers to
use the new shared function to remove duplication.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioThemeTableViewCell.swift`:
- Around line 8-133: This file contains an entire commented-out implementation
of StudioThemeTableViewCell (including commented imports and methods like
bind(reactor:), setupUI(), setupAutoLayout(), setupAttributes(), bindInput(_:),
bindOutput(_:), setCell(_:), createContentDataSource(), and
UICollectionViewDelegateFlowLayout conformance) which should be cleaned up:
either restore/uncomment the real implementation if it's intended to be used
(ensure imports and the StudioThemeTableViewCell class and its methods are
active) or remove the file and any dead commented blocks if it’s not needed;
specifically delete or uncomment the large block between the commented import
lines and the end of the class so references like StudioThemeTableViewCell,
setCell, bindInput, bindOutput, and createContentDataSource no longer exist only
as comments.

In `@14th-team5-iOS/Data/Sources/APIs/Post/PostAPIs.swift`:
- Around line 18-24: The PostAPIs cases use raw String for the `type` parameter
which allows invalid values at runtime; introduce a dedicated enum (e.g.,
PostType or AIImageType) with explicit cases for allowed types and replace the
String associated values on PostAPIs cases fetchAICount, createPost, and
createAIPost to use that enum instead, update the enum to conform to
RawRepresentable/CustomStringConvertible or provide a computed property to emit
the string when building the request, and then update all call sites and the
request-building logic to use the new enum value rather than raw strings so
compilation enforces valid types.

In `@14th-team5-iOS/Data/Sources/APIs/Post/PostListAPIWorker.swift`:
- Around line 80-91: The doc comment above func createAIPost(query:
CreatePostQuery, body: CreatePostRequestDTO) ->
Observable<CreatePostResponseDTO?> is a copy of createPost and should be updated
to describe AI post creation: change the summary line to indicate "AI 게시물 생성"
(or similar), keep HTTP Method : POST, list parameters with their concrete types
(query: CreatePostQuery — include that query.type represents the AI post type,
body: CreatePostRequestDTO — note any AI-specific fields expected), and the
return type CreatePostResponseDTO; ensure the comment is accurate and concise
and replaces the old createPost wording so readers understand this method
creates AI-generated posts.

In `@14th-team5-iOS/Domain/Sources/Entities/PostList/CreatePostRequest.swift`:
- Around line 23-32: The CreatePostRequest initializer currently assigns
latitude/longitude directly; add domain-level validation inside the initializer
(CreatePostRequest init) to ensure latitude is in -90...90 and longitude is in
-180...180, and handle invalid values by failing early (make the init failable,
throw a DomainError like InvalidCoordinates, or use precondition with a clear
message) instead of accepting arbitrary Doubles; update property assignment to
only set latitude/longitude when they pass the range checks and include a clear
error/handling path (e.g., InvalidCoordinates) so callers and logs can identify
bad input.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0daee45f-1598-4b7b-9833-47769d32597d

📥 Commits

Reviewing files that changed from the base of the PR and between bc28787 and a4d3cdd.

📒 Files selected for processing (45)
  • 14th-team5-iOS/Bibbi/Project.swift
  • 14th-team5-iOS/Bibbi/Project/Sources/Presentation/Location/Model/LocationPlace.swift
  • 14th-team5-iOS/Bibbi/Sources/Application/DIContainer/StudioDIContainer.swift
  • 14th-team5-iOS/Bibbi/Sources/Application/Navigator/CameraDisplayNavigator.swift
  • 14th-team5-iOS/Bibbi/Sources/Application/Navigator/StudioPageNavigator.swift
  • 14th-team5-iOS/Bibbi/Sources/Application/Navigator/Wrapper/Studio/StudioWrapper.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Calendar/View/Cell/MemoriesCalendarPostCell.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Calendar/View/MemoriesCalendarPostImageView.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Camera/CameraDisplayViewController.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Camera/Reactor/CameraDisplayViewReactor.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/Model/StudioSection.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/MainViewReactor.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/Reactor/pages/StudioPageReactor.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/ViewControllers/pages/StudioPageViewController.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/Cell/MainFamilyCollectionViewCell.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/Cell/MainPostCollectionViewCell.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/Cell/StudioThemeTableViewCell2.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/StudioTableViewCell.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Location/ViewControllers/LocationViewController.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/PostDetail/Views/PostDetailCollectionViewCell.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Studio/Reactor/StudioReactor.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioBannerView.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioThemeTableViewCell.swift
  • 14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioViewController.swift
  • 14th-team5-iOS/Core/Sources/Bibbi/BBCommons/BBSegmentedControl/BibbiSegmentedControl.swift
  • 14th-team5-iOS/Core/Sources/Trash/BBNetwork/API.swift
  • 14th-team5-iOS/Data/Sources/APIs/Calendar/DataMapping/ArrayResponseDailyCalendarResponseDTO.swift
  • 14th-team5-iOS/Data/Sources/APIs/Post/DataMapping/AIGroupListResponseDTO.swift
  • 14th-team5-iOS/Data/Sources/APIs/Post/DataMapping/AIPostCountResponseDTO.swift
  • 14th-team5-iOS/Data/Sources/APIs/Post/DataMapping/CreatePostRequestDTO.swift
  • 14th-team5-iOS/Data/Sources/APIs/Post/DataMapping/PostListResponseDTO.swift
  • 14th-team5-iOS/Data/Sources/APIs/Post/PostAPIs.swift
  • 14th-team5-iOS/Data/Sources/APIs/Post/PostListAPIWorker.swift
  • 14th-team5-iOS/Data/Sources/Repositories/AppRepository.swift
  • 14th-team5-iOS/Data/Sources/Repositories/PostRepository.swift
  • 14th-team5-iOS/Domain/Sources/Entities/Calendar/ArrayResponseDailyCalendarEntity.swift
  • 14th-team5-iOS/Domain/Sources/Entities/PostList/CreatePostRequest.swift
  • 14th-team5-iOS/Domain/Sources/Entities/PostList/PostEntity.swift
  • 14th-team5-iOS/Domain/Sources/Entities/Studio/AIPostListQuery.swift
  • 14th-team5-iOS/Domain/Sources/Entities/Studio/StudioThemeEntity.swift
  • 14th-team5-iOS/Domain/Sources/Repositories/StudioRepository.swift
  • 14th-team5-iOS/Domain/Sources/UseCases/Studio/FetchStudioCountUseCase.swift
  • 14th-team5-iOS/Domain/Sources/UseCases/Studio/FetchStudioPostListUseCase.swift
  • 14th-team5-iOS/Domain/Sources/UseCases/Studio/FetchStudioThemeUseCase.swift
  • Tuist/ProjectDescriptionHelpers/Project+Templates.swift

Comment on lines +117 to +123
func setLocation(address: String?) {
if let address, !address.isEmpty {
locationLabel.text = address
locationContainerView.isHidden = false
} else {
locationContainerView.isHidden = 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.

⚠️ Potential issue | 🟡 Minor

공백-only 주소에 대한 처리 보완이 필요합니다.

현재 " " 같은 값도 유효 주소로 판단되어 위치 배지가 표시될 수 있습니다. trim 후 비어있는지 검사하는 방식이 안전합니다.

수정 예시
     func setLocation(address: String?) {
-        if let address, !address.isEmpty {
-            locationLabel.text = address
+        let normalized = address?.trimmingCharacters(in: .whitespacesAndNewlines)
+        if let normalized, !normalized.isEmpty {
+            locationLabel.text = normalized
             locationContainerView.isHidden = false
         } else {
+            locationLabel.text = nil
             locationContainerView.isHidden = true
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Calendar/View/MemoriesCalendarPostImageView.swift`
around lines 117 - 123, In setLocation(address: String?) update the validity
check to trim whitespace before testing emptiness so that addresses like "   "
are treated as empty; if the trimmed address is non-empty set locationLabel.text
= trimmedAddress and show locationContainerView, otherwise hide
locationContainerView — adjust references in this function (setLocation,
locationLabel, locationContainerView) accordingly.

Comment on lines +96 to 101
if let url = URL(string: data.imageURL) {
imageView.kf.setImage(with: url)
indicator.stopAnimating()
} else {
imageView.image = DesignSystemAsset.emptyCaseGraphicEmoji.image
}

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 | 🟡 Minor

유효하지 않은 URL 분기에서도 로딩 인디케이터를 중지하세요.

Line 99-101 경로에서는 fallback 이미지를 넣지만 indicator.stopAnimating()가 호출되지 않아 스피너가 계속 보일 수 있습니다.

수정 제안 diff
     private func setCell(_ data: PostEntity) {
         if let url = URL(string: data.imageURL) {
             imageView.kf.setImage(with: url)
             indicator.stopAnimating()
         } else {
             imageView.image = DesignSystemAsset.emptyCaseGraphicEmoji.image
+            indicator.stopAnimating()
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/Cell/MainPostCollectionViewCell.swift`
around lines 96 - 101, The image loading branch in MainPostCollectionViewCell
stops the activity indicator only on successful URL creation/Kingfisher load
path, but the else branch that assigns the fallback image (imageView.image =
DesignSystemAsset.emptyCaseGraphicEmoji.image) never calls
indicator.stopAnimating(), leaving the spinner visible; update the else branch
(or refactor so indicator.stopAnimating() is always executed after attempting to
set the image) to call indicator.stopAnimating() when data.imageURL is not a
valid URL so the spinner is always stopped regardless of URL validity.

Comment on lines +123 to +136
toolTipView.do {
$0.superview = infoButton
}

bannerImageView.do {
$0.clipsToBounds = true
$0.contentMode = .scaleAspectFill
$0.layer.cornerRadius = 24
}
}

@objc func didTapInfo() {
toolTipView.isHidden.toggle()
}

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 | 🟡 Minor

툴팁 표시 상태가 셀 재사용 시 누적될 수 있습니다.

Line 135에서 토글만 수행하고 초기/재사용 리셋이 없어, 스크롤 후 다른 셀에 이전 툴팁 상태가 남을 수 있습니다. 초기 hidden 설정과 prepareForReuse() 리셋을 추가하는 게 안전합니다.

수정 예시
     func setupAttributes() {
         backgroundColor = .clear
         selectionStyle = .none
@@
         toolTipView.do {
             $0.superview = infoButton
+            $0.isHidden = true
         }
@@
     }
+
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        toolTipView.isHidden = true
+    }
📝 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
toolTipView.do {
$0.superview = infoButton
}
bannerImageView.do {
$0.clipsToBounds = true
$0.contentMode = .scaleAspectFill
$0.layer.cornerRadius = 24
}
}
@objc func didTapInfo() {
toolTipView.isHidden.toggle()
}
toolTipView.do {
$0.superview = infoButton
$0.isHidden = true
}
bannerImageView.do {
$0.clipsToBounds = true
$0.contentMode = .scaleAspectFill
$0.layer.cornerRadius = 24
}
}
override func prepareForReuse() {
super.prepareForReuse()
toolTipView.isHidden = true
}
`@objc` func didTapInfo() {
toolTipView.isHidden.toggle()
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Home/Views/Cell/StudioThemeTableViewCell2.swift`
around lines 123 - 136, The cell's tooltip visibility can persist across reused
cells because didTapInfo() only toggles state; ensure toolTipView has a
deterministic initial state and is reset on reuse by (1) setting
toolTipView.isHidden = true when configuring/initializing the view (where
toolTipView.do { ... } is used) and (2) overriding prepareForReuse() in
StudioThemeTableViewCell2 to explicitly set toolTipView.isHidden = true (and
call super.prepareForReuse()), leaving didTapInfo() as the toggle handler.

Comment on lines +114 to +116
let isInPeriod = StudioReactor.isInThemePeriod(theme: reactor.currentState.theme)
uploadButton.isHidden = !isInPeriod

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 | 🟡 Minor

테마 기간 판정값을 1회만 캡처하면 날짜 경계에서 UI 상태가 stale 될 수 있습니다.

현재 isInPeriod가 바인딩 시점에만 계산되어, 앱이 살아있는 동안 날짜가 바뀌면 업로드 버튼 노출/스타일이 실제 기간과 어긋날 수 있습니다. 판정은 이벤트 시점마다 다시 계산하는 편이 안전합니다.

수정 예시
-        let isInPeriod = StudioReactor.isInThemePeriod(theme: reactor.currentState.theme)
-        uploadButton.isHidden = !isInPeriod
+        uploadButton.isHidden = !StudioReactor.isInThemePeriod(theme: reactor.currentState.theme)

         reactor.pulse(\.$isEnabledUpload)
             .distinctUntilChanged()
             .bind(with: self, onNext: { owner, isEnabled in
-                guard isInPeriod else { return }
+                let isInPeriod = StudioReactor.isInThemePeriod(theme: reactor.currentState.theme)
+                owner.uploadButton.isHidden = !isInPeriod
+                guard isInPeriod else { return }
                 owner.updateUploadButtonLayout(isEnabled)
             })
             .disposed(by: disposeBag)

Also applies to: 126-127

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

In
`@14th-team5-iOS/Bibbi/Sources/Presentation/Studio/ViewControllers/StudioViewController.swift`
around lines 114 - 116, The UI currently captures
StudioReactor.isInThemePeriod(theme:) once and sets uploadButton.isHidden
statically, causing stale UI across date boundaries; change the logic to
recompute the period check at each relevant event (e.g., in viewWillAppear, on
reactor state updates, or via a daily timer) and update uploadButton
visibility/style accordingly. Locate the one-time call to
StudioReactor.isInThemePeriod(theme: reactor.currentState.theme) and replace it
with a reactive or event-driven update path that re-evaluates
StudioReactor.isInThemePeriod using reactor.currentState.theme whenever the view
appears or the theme/state changes (also apply same change for the similar check
at lines 126-127), and ensure the update modifies uploadButton.isHidden (and any
style properties) each time.

switch self {
case let .auth(token): return "Bearer \(token)"
case .xAppKey: return "9aaf231b-424c-4376-b682-cff03d96f326" // TODO: - 번들에서 가져오기
case .xAppKey: return "cec77ebf-b7fa-4d10-8746-eca496acbf6c" // TODO: - 번들에서 가져오기

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 | 🟠 Major

X-APP-KEY를 하드코딩으로 중복 관리하면 경로별 키 불일치가 재발합니다.

현재 키가 여러 위치에서 별도 문자열로 유지되고 있어(예: AppRepository, Bundle+Ext) 호출 경로에 따라 인증 키가 달라질 수 있습니다. 단일 소스로 통합해 주세요.

🔧 제안 수정
- case .xAppKey: return "cec77ebf-b7fa-4d10-8746-eca496acbf6c" // TODO: - 번들에서 가져오기
+ case .xAppKey: return Bundle.main.xAppKey
// Core/Sources/Extensions/Bundle+Ext.swift
public var xAppKey: String {
    "cec77ebf-b7fa-4d10-8746-eca496acbf6c"
}
📝 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
case .xAppKey: return "cec77ebf-b7fa-4d10-8746-eca496acbf6c" // TODO: - 번들에서 가져오기
case .xAppKey: return Bundle.main.xAppKey
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@14th-team5-iOS/Core/Sources/Trash/BBNetwork/API.swift` at line 56, The
.xAppKey case in API.swift currently returns a hardcoded string which duplicates
keys across the codebase (e.g., AppRepository, Bundle+Ext); replace that
hardcoded value so all callers read the single source of truth (use the existing
Bundle+Ext public var xAppKey or a centralized AppRepository.xAppKey constant)
by changing the .xAppKey return to reference that property instead of the
literal string and remove other duplicated literals to ensure all paths use the
same symbol.

// reactor.state
// .map { $0.memoriesCount }
// .distinctUntilChanged()
// .bind(to: bannerView.rx.count)

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.

p5
사용하지 않는 주석 추후 제거

reactor.state.map { $0.post.address }
.distinctUntilChanged()
.withUnretained(self)
.subscribe { owner, address in

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.

p2
UI 작업은 main thread 보장 받을 수 있도록 bind메서드 사용

////
//// StudioThemeTableViewCell.swift
//// Bibbi
////

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.

p1
사용하지 않는 파일 제거

method: .post,
path: "/posts",
queryParameters: [
.type: "AI_IMAGE",

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.

p2
"AI_IMAGE" 하드 코딩 추후 개선

@Do-hyun-Kim Do-hyun-Kim merged commit ddafa2e into develop Apr 14, 2026
2 checks passed
@Do-hyun-Kim Do-hyun-Kim deleted the feat/#add-location branch April 14, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants