- 매장 선택 → 연/월 → 월 캘린더(날짜별 제품 사진 썸네일) → 날짜 상세 플로우로 전면 재작성 - 스캔 플로우: 촬영 → 재촬영/사용 → 그 달 날짜만 선택 → 저장 후 카메라 자동 복귀 (손전등 지원) - 날짜 상세 다중 선택 삭제 (전체 선택/해제 + 일괄 삭제 확인) - 다크 전용 민트 테마, SwiftData 모델(Store ↔ ProductRecord, 원본+썸네일 분리 저장) - 밝은 민트 앱 아이콘(SVG 원본 포함) + 런치 스크린/인앱 스플래시 - CLAUDE.md 새 구조 기준으로 재작성 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.1 KiB
5.1 KiB
Expiranner Project Rules
1. 프로젝트 개요
- 이름: Expiranner — 매장별 유통기한 점검 도구 (개인용, 배포 없음)
- 루트:
Expiranner/(모든 작업은 이 루트에서 수행) - 소스 폴더:
IOS/— pbxproj가 파일시스템 동기화 그룹(PBXFileSystemSynchronizedRootGroup)이라 이 폴더에 Swift 파일을 넣기만 하면 빌드에 포함된다.Expiranner하위 폴더를 새로 만들지 말 것. - 프레임워크: SwiftUI + SwiftData (Core Data 금지)
- 타깃: iOS 17+, iPhone 중심
- 언어: 모든 UI 텍스트와 로그는 한국어
- 외관: 다크 모드 전용 (
.preferredColorScheme(.dark)), 라이트 모드 대응 안 함 - 디자인 토큰:
Theme.swift(배경#0B0E13, 카드#161B25, 민트 액센트#3BE39F, rounded 폰트 디자인)
2. 핵심 플로우 (이 동작이 앱의 전부)
- 매장 선택 (
StoreListView): 앱 첫 화면. 매장 목록 표시,+로 추가, 컨텍스트 메뉴로 이름 변경/삭제(기록 연쇄 삭제). - 연/월 선택 (
YearMonthView): 연도 좌우 이동 + 1~12월 그리드, 월별 기록 개수 배지. - 월 캘린더 (
MonthCalendarView): 날짜 셀마다 그 날짜가 유통기한인 제품 사진 썸네일 + 개수 배지. 좌우 화살표로 월 이동. 오늘 날짜 민트 테두리. - 날짜 상세 (
DayDetailView): 시트로 그 날짜의 사진 그리드, D-day 칩(D-n / 오늘까지 / n일 지남), 탭하면 전체화면 뷰어(PhotoViewerView, 스와이프 + 삭제). [선택] 버튼으로 다중 선택 모드 → 전체 선택/해제 + 일괄 삭제(확인 대화상자). - 스캔 플로우 (
CaptureFlowView, 전체화면): FAB(카메라 버튼) → 촬영 → [재촬영]/[사용하기] → 현재 보고 있는 달의 날짜만 선택 가능한 미니 달력 → [저장하기] → 성공 햅틱 + 토스트 → 자동으로 카메라 복귀.X로 캘린더 복귀. 손전등 토글 지원. - 로딩 화면: 시스템 런치 스크린(루트
Info.plist의UILaunchScreen—LaunchBackground색 +LaunchIcon이미지) → 인앱 스플래시(ExpirannerApp.swift의SplashView, 약 1초 후 페이드아웃)로 이어짐. 런치 스크린과 스플래시의 글리프 크기(110pt)를 맞춰 끊김이 없게 유지할 것.
3. 데이터 모델 (Models.swift)
@Model final class Store {
var name: String
var createdAt: Date
@Relationship(deleteRule: .cascade, inverse: \ProductRecord.store)
var records: [ProductRecord] = []
}
@Model final class ProductRecord {
var expiryDate: Date // 자정으로 정규화
var createdAt: Date
@Attribute(.externalStorage) var imageData: Data // 최대 1600px JPEG
var thumbnailData: Data // 최대 360px JPEG (캘린더/그리드용)
var store: Store?
}
- 뷰에서는
@Query로 전체를 받고$0.store === store로 메모리 필터링한다 (Predicate로 관계 비교하지 말 것 — iOS 17 호환성 문제 회피). - 스키마 변경 시
ExpirannerApp.init의 저장소 초기화 폴백이 기존 데이터를 지우고 재생성한다.
4. 파일 구성 (IOS/)
ExpirannerApp.swift— 엔트리, ModelContainer(실패 시 저장소 리셋 폴백)Models.swift— Store, ProductRecordTheme.swift— 색상/카드 스타일/햅틱(Haptics)/달력 유틸(KoreanCalendar)/이미지 다운스케일StoreListView.swift,YearMonthView.swift,MonthCalendarView.swift,DayDetailView.swiftCaptureFlowView.swift— 촬영→확인→날짜선택→저장 상태 머신CameraService.swift— AVCaptureSession 래퍼(@MainActor) +CameraPreview
5. 코딩 규칙
- 연도 등 숫자를 Text에 넣을 때 반드시
Text(verbatim:)사용 (천 단위 구분 방지: "2,026년" 버그). - 날짜 계산은 전부
KoreanCalendar.calendar(gregorian + ko_KR) 사용. - 새 모듈 심볼 사용 시 명시적 import 필요 (
MEMBER_IMPORT_VISIBILITY켜져 있음 — 예:@Published엔import Combine). - 저장/삭제 후
try? context.save()+ 햅틱 호출이 관례. - 불필요한 메타데이터 필드, 복잡한 설정 화면, 알림 기능 등을 임의로 추가하지 말 것.
6. 빌드 / 검증
xcodebuild -project Expiranner.xcodeproj -scheme Expiranner \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.5' \
-configuration Debug build CODE_SIGNING_ALLOWED=NO
- 시뮬레이터에서는 카메라가 동작하지 않으므로 스캔 플로우는 실기기에서 확인.
- 앱 아이콘 원본: 루트의
AppIcon.svg(밝은 민트 배경) →qlmanage -t -s 1024로 렌더 → PIL로 알파 제거 →IOS/Assets.xcassets/AppIcon.appiconset/AppIcon.png. - 런치 글리프(
LaunchIcon.imageset, 투명 배경 필수): qlmanage가 SVG를 흰 배경으로 렌더하는 경우가 있으므로 PIL로 도형을 직접 그려 생성했다(2x=220px, 3x=330px). 루트LaunchIcon.svg는 도형 좌표 원본. GENERATE_INFOPLIST_FILE=YES+INFOPLIST_FILE=Info.plist병합 구성 — 루트Info.plist에는 UILaunchScreen만 두고 나머지 키는 빌드 설정(INFOPLIST_KEY_*)으로 관리.