fix(ui): smooth expand/collapse animation for time input rows

- 시각 입력 값 행의 펼침/접힘이 "확" 스냅된다는 실기기 피드백 → 조건부 삽입(if) 대신 높이 0↔216pt 프레임 보간(.smooth 0.3s, 바운스 없음)으로 변경 — List/Form 행 높이가 매 프레임 따라와 부드럽게 열리고 닫힘. 공용 CollapsibleTimeWheel 한 곳 수정으로 6곳(기록 편집 4시트·마감 시각·하루 시작 시간) 전부 적용
- 휠은 Color.clear의 overlay(top 고정)로 띄움 — UIDatePicker의 고유 높이(216)가 레이아웃에 개입해 접힘 상태에 빈 공간이 남던 문제(1차 구현에서 발견) 방지. 접힘 시 clipped+터치 차단+보이스오버 숨김
- 값 반영 경로·검증(종료<시작 경고 등) 불변, 새 문자열 없음(카탈로그 클린)
- 검증: 세션 수정 접힘(빈 공간 없음)·설정 펼침 시뮬 스크린샷, Debug/Store 빌드 성공. CLAUDE.md §6.5에 보간 구조 유지 규칙 기재

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CkLDi8Lr21vGqo4SjZaTp
This commit is contained in:
songyc macbook 2026-07-19 09:59:33 +09:00
parent 7baa7a424c
commit 459c85700f
2 changed files with 19 additions and 5 deletions

View File

@ -201,7 +201,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f
### 6.5 기록 탭 (`HistoryView`, `RecordEditors`)
- **범위 조회**: 기록은 전량 로드하지 않고 선택 날짜가 속한 **주 범위**만 @Query로 가져온다(`HistoryRecordsView` — SwiftData 동적 쿼리 패턴: 날짜가 바뀌면 새 조건으로 재생성). 하루 경계 걸침·진행 중 세션은 "시작 < 범위끝 AND (종료 없음 OR 종료 > 범위시작)" 겹침 조건으로 포함
- 날짜 헤더(화살표 이동, 날짜 탭=달력 팝오버, "오늘" 버튼) + 목록/타임테이블 세그먼트
- 목록: 시간 기록(시작~종료·총 시간·메모)·횟수 기록(시각·+n·메모), 탭하면 수정 시트. **수정·추가 시트의 시각 입력은 상시 노출 휠 다이얼**(`RecordEditors.swift` RecordTimeWheel) — 컴팩트 팝오버는 다이얼 감속 중 닫히면 정착값이 유실되는 실기기 보고가 있어 폐기. `in:` 범위 제약도 하한 근처 선택을 소리 없이 되감아 금지(검증은 종료<시작 경고 문구+저장 비활성). 편집 상태는 단위 절사(flooredToMinute) 실측정 기록의 찌꺼기(14:32:47) 남으면 다이얼로 맞춘 1시간이 59분으로 표시되는 어긋남 방지. **앱의 모든 시각 입력은 `CollapsibleTimeWheel`**(RecordEditors.swift) 평소엔 (라벨+현재 +셰브론), 탭하면 인라인 (RecordTimeWheel) 펼침. 상시 노출 휠이 어수선하다는 실기기 피드백으로 기록 편집 4시트·다짐 '마감 시각설정 '하루 시작 시간' 전부 통일(기본 접힘). 팝오버 금지 이유(감속 닫힘 정착값 유실) 범위 제약 금지· 절사 규칙은 그대로
- 목록: 시간 기록(시작~종료·총 시간·메모)·횟수 기록(시각·+n·메모), 탭하면 수정 시트. **수정·추가 시트의 시각 입력은 상시 노출 휠 다이얼**(`RecordEditors.swift` RecordTimeWheel) — 컴팩트 팝오버는 다이얼 감속 중 닫히면 정착값이 유실되는 실기기 보고가 있어 폐기. `in:` 범위 제약도 하한 근처 선택을 소리 없이 되감아 금지(검증은 종료<시작 경고 문구+저장 비활성). 편집 상태는 단위 절사(flooredToMinute) 실측정 기록의 찌꺼기(14:32:47) 남으면 다이얼로 맞춘 1시간이 59분으로 표시되는 어긋남 방지. **앱의 모든 시각 입력은 `CollapsibleTimeWheel`**(RecordEditors.swift) 평소엔 (라벨+현재 +셰브론), 탭하면 인라인 (RecordTimeWheel) 펼침. 상시 노출 휠이 어수선하다는 실기기 피드백으로 기록 편집 4시트·다짐 '마감 시각설정 '하루 시작 시간' 전부 통일(기본 접힘). **펼침/접힘은 조건부 삽입(if)이 아니라 높이 0↔216 프레임 보간**(.smooth 0.3s) 삽입 방식은 List 높이가 스냅되어 '' 열리는 느낌(실기기 피드백). 휠은 Color.clear의 overlay로 띄워 UIDatePicker 고유 높이가 레이아웃에 개입하지 않게 한다(접힘 상태 공간 버그 방지 구조를 유지할 ). 팝오버 금지 이유(감속 닫힘 정착값 유실) 범위 제약 금지· 절사 규칙은 그대로
- 타임테이블: 24시간 축(하루 시작 시간 기준), 하루/일주일 전환(주간 보기에서 화살표는 7일씩 점프). 시간=색 블록, 횟수=색 점. 블록/점 탭=수정
- 필터: `RecordFilterSheet`(공용) — 꼬리표 아래 행동이 그룹된 트리, **"제외한 행동 ID" 집합**으로 관리해 기본이 전체 선택. 활성 시 초록 칩 표시
- 내보내기: 현재 날짜/모드/필터 그대로 `ExportBuilder.history` 스냅숏 → 이미지 시트

View File

@ -54,10 +54,16 @@ struct CollapsibleTimeWheel: View {
self._isExpanded = State(initialValue: initiallyExpanded)
}
/// UIDatePicker ( 216pt) /
/// (if) 0 .
/// List/Form ""
/// ( ), .
private static let wheelHeight: CGFloat = 216
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Button {
withAnimation(.snappy(duration: 0.25)) {
withAnimation(.smooth(duration: 0.3)) {
isExpanded.toggle()
}
} label: {
@ -77,9 +83,17 @@ struct CollapsibleTimeWheel: View {
.buttonStyle(.plain)
.accessibilityLabel(Text(label))
.accessibilityValue(Text(selection, format: valueFormat))
if isExpanded {
RecordTimeWheel(label: label, selection: $selection, components: components)
}
// Color.clear (0 ) overlay
// UIDatePicker
Color.clear
.frame(height: isExpanded ? Self.wheelHeight : 0)
.overlay(alignment: .top) {
RecordTimeWheel(label: label, selection: $selection, components: components)
}
.clipped()
.opacity(isExpanded ? 1 : 0)
.allowsHitTesting(isExpanded)
.accessibilityHidden(!isExpanded)
}
}