From 6e0af0c33cdce5887f5a48e5242577e5a191e431 Mon Sep 17 00:00:00 2001 From: songyc macbook Date: Tue, 14 Jul 2026 13:55:59 +0900 Subject: [PATCH] fix(widget): make the whole interactive cell tappable, not just its visible content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인터랙티브 위젯(①행동 실행·③다짐 진행률)에서 셀의 일부 지점을 누르면 인텐트가 실행되지 않고 앱이 열리던 버그 수정. 원인: .plain 스타일 Button은 라벨의 투명한 영역(Spacer로 벌어진 공간, 여백, 링 안쪽)을 탭 판정에서 제외하는 경우가 있어, 해당 지점의 탭이 버튼에 잡히지 않고 위젯 기본 동작(앱 열기)으로 샜다. 수정: 두 위젯의 버튼 라벨 끝에 .contentShape(.rect)로 히트 영역을 셀 프레임 전체로 명시 — 아이콘·글자·프로그레스 링은 물론 셀 안 빈 공간까지 어디를 눌러도 인텐트가 실행된다. 시각적 변화 없음. (③의 꼬리표 대상 다짐 셀은 표시 전용이라 기존대로 앱 열기) 검증: Debug·Store 빌드 성공, 위젯 미리보기 스크린샷으로 레이아웃 무영향 확인. CLAUDE.md §10에 히트 영역 규칙 추가. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7 --- myApp/HaruDanim/CLAUDE.md | 1 + myApp/HaruDanim/Widgets/ActionRunWidget.swift | 3 +++ myApp/HaruDanim/Widgets/QuestRingWidget.swift | 3 +++ 3 files changed, 7 insertions(+) diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index c05e0e6..69cba3b 100644 --- a/myApp/HaruDanim/CLAUDE.md +++ b/myApp/HaruDanim/CLAUDE.md @@ -251,6 +251,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}" - 슬롯 규칙: 미선택이면 기본 대상 채움, 선택했으면 순서 존중 + 모자란 슬롯은 점선 빈 칸. '선택 안 함' 센티널 `NoneEntityID` - **타임라인 예산 전략**(`WidgetRefresh`): 주기 폴링 없음. 값 변경은 `DataChange.commit`/인텐트의 reloadAllTimelines가 담당. 측정 중일 때만 10분 간격 미래 엔트리 1시간치+.atEnd, 평상시엔 하루 경계(최대 4h) 안전망 - **버튼 탭 즉시 피드백**: ①③의 변하는 값(누적·퍼센트·링)에 `invalidatableContent()` — 탭 즉시 '갱신 중' 표시로 바뀌어 새 타임라인 도착(1~3초, WidgetKit 구조상 단축 불가) 전에도 탭 접수를 보여 줌(중복 탭 방지) +- **버튼 히트 영역**: `Button(intent:)` 라벨 끝에 `.contentShape(.rect)` 필수 — plain 버튼은 라벨의 투명 영역(Spacer·여백·링 안쪽)을 탭 판정에서 빼는 경우가 있어, 그 지점 탭이 인텐트 대신 위젯 기본 동작(앱 열기)으로 새는 버그가 있었다. ③의 꼬리표 대상 다짐 셀은 버튼이 없어(표시 전용) 셀 전체가 앱 열기 — 의도된 동작 - 확장 프로세스는 `IntentStore.refresh()`로 작업마다 컨테이너를 새로 열어 최신 데이터 보장 (실패 시 직전 컨테이너 유지 — 크래시 방지) - `RunActionIntent`는 AppEntity 대신 **UUID 문자열 파라미터** — 버튼 탭 지연·유실 방지의 핵심 diff --git a/myApp/HaruDanim/Widgets/ActionRunWidget.swift b/myApp/HaruDanim/Widgets/ActionRunWidget.swift index 46ade6d..5e938f0 100644 --- a/myApp/HaruDanim/Widgets/ActionRunWidget.swift +++ b/myApp/HaruDanim/Widgets/ActionRunWidget.swift @@ -185,6 +185,9 @@ struct ActionRunCellView: View { } } } + // 히트 영역을 셀 프레임 전체로 명시 — plain 버튼은 라벨의 투명한 부분(Spacer·여백)을 + // 탭 판정에서 빼는 경우가 있어, 그 지점을 누르면 인텐트 대신 위젯 기본 동작(앱 열기)이 됐다 + .contentShape(.rect) } .buttonStyle(.plain) } diff --git a/myApp/HaruDanim/Widgets/QuestRingWidget.swift b/myApp/HaruDanim/Widgets/QuestRingWidget.swift index c1f8198..604548b 100644 --- a/myApp/HaruDanim/Widgets/QuestRingWidget.swift +++ b/myApp/HaruDanim/Widgets/QuestRingWidget.swift @@ -157,6 +157,9 @@ struct QuestRingActionCellView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(fullBleed ? 10 : 6) .background(WidgetCellBackground(fullBleed: fullBleed)) + // 히트 영역을 셀 프레임 전체로 명시 — plain 버튼은 라벨의 투명한 부분(링 안쪽·여백)을 + // 탭 판정에서 빼는 경우가 있어, 그 지점을 누르면 인텐트 대신 위젯 기본 동작(앱 열기)이 됐다 + .contentShape(.rect) } private var percentText: String {