fix(diary): stop pencil strokes vanishing when zooming the note page
아이패드 일기 노트에서 필기 후 핀치줌하면 획이 사라지고, 다시 그리는 동안엔 잠깐 보였다가 획을 떼면 또 사라지던 버그 수정. 원인: 줌 배율이 바뀔 때마다 SharpPencilCanvasView가 PencilKit 내부의 모든 서브레이어에 contentsScale 변경 + setNeedsDisplay()를 강제했는데, PencilKit의 획 타일 레이어는 렌더러가 contents를 직접 채우는 방식이라 setNeedsDisplay()가 기존 픽셀만 지우고 다시 그려 주지 않음. 그리는 동안엔 라이브 렌더로 보이다가 canvasViewDrawingDidChange → applySharpness에서 다시 지워짐. 수정: 배율 적용을 뷰 수준 contentScaleFactor 재귀로 한정하고 레이어 직접 조작·setNeedsDisplay 제거 — PencilKit이 자기 타이밍에 새 배율로 재렌더링해 획이 유지되면서 선명도(5916e7의 목적)도 유지된다. 검증(iPad 시뮬레이터): 시드에 필기 획 2줄 추가 + -diaryZoomScale 런치 인자(프로그램 줌) 신설 → 수정 전 2배 줌에서 획 소실 재현, 수정 후 동일 조건에서 획 유지·선명 확인. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
parent
973bb50cba
commit
430ec89341
@ -191,7 +191,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
|
||||
- **요약 페이지 섹션** (표시·순서는 "첫 화면 구성" 시트, 전 날짜 공통, standard defaults): 오늘 기분(대형 정사각 타일: 이모지/사진) · 요약 지표 · 이 날의 목표(그날 기준 다짐 현황, 주간/월간은 그날까지 누적) · 오늘 할 일 · 타임테이블 · 기록 상세 · 통계 막대. 화면 720pt 이상이면 타임테이블 좌측 고정 2컬럼
|
||||
- 타임테이블 카드 우상단 버튼: **필터**(그날 기록된 행동만 나열된 팝업에서 표시 선택, 날짜별 저장 `hiddenActionIDs`, 숨긴 개수 노란 배지 — 타임테이블에만 적용, 요약 수치·기록 목록은 전체 유지) + **캘린더**(그날의 EKEvent 선택, 날짜별 저장, 외곽선 스타일 블록). 둘 다 내보내기에 동일 반영
|
||||
- **프리미엄 만료 열람 모드**: 이미 쓴 일기(hasContent)가 있으면 잠금 화면 대신 달력을 열람 전용으로 연다 — 배너 + 일기 있는 날짜만 활성, `environment(\.diaryReadOnly)`로 전 화면의 편집 UI(기분/할일/필기/필터/캘린더/⋯메뉴) 비활성, 열람 진입은 엔트리를 생성하지 않음. 일기를 쓴 적 없으면 기존 잠금 안내
|
||||
- 노트 페이지: 768×1024 논리 좌표 고정 + scaleEffect. 배치 요소: 사진·도형(사각/원/화살표/선)·**텍스트 상자**(키보드 입력, 배치 모드에서 '글 수정'으로 내용·색 편집). ⋯ 메뉴 '페이지 순서 변경'으로 노트 페이지 드래그 재배열(요약은 항상 첫 장). **실기기 필기는 펜슬 전용, 손가락은 이동/줌만**(시뮬레이터는 anyInput). 핀치줌 최대 4배, `SharpPencilCanvasView`가 contentsScale을 줌에 맞춰 유지(획 선명도). 줄 노트(간격 4단계)·사진·도형(사각/원/화살표/선) 배치 모드(드래그·핀치 크기 조절, 저장은 비율 좌표)
|
||||
- 노트 페이지: 768×1024 논리 좌표 고정 + scaleEffect. 배치 요소: 사진·도형(사각/원/화살표/선)·**텍스트 상자**(키보드 입력, 배치 모드에서 '글 수정'으로 내용·색 편집). ⋯ 메뉴 '페이지 순서 변경'으로 노트 페이지 드래그 재배열(요약은 항상 첫 장). **실기기 필기는 펜슬 전용, 손가락은 이동/줌만**(시뮬레이터는 anyInput). 핀치줌 최대 4배, `SharpPencilCanvasView`가 줌에 맞춰 래스터 배율 유지(획 선명도). ⚠️ 배율 적용은 **뷰의 contentScaleFactor만** — 서브레이어 contentsScale+setNeedsDisplay를 직접 건드리면 PencilKit 획 타일 픽셀이 지워진 채 재렌더되지 않아 확대 시 필기가 사라진다(수정된 버그). 줄 노트(간격 4단계)·사진·도형(사각/원/화살표/선) 배치 모드(드래그·핀치 크기 조절, 저장은 비율 좌표)
|
||||
|
||||
### 6.8 설정 탭 (`SettingsView`)
|
||||
- 테마(라이트/다크 — 즉시 적용, App Group 저장으로 위젯 공유) / 언어(재시작 적용, AppleLanguages 오버라이드)
|
||||
@ -300,7 +300,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
|
||||
| 기록/통계 | `-historyMode timetable` `-historyWeekly` `-excludeActions "이름,이름"` `-historyShowFilter` `-statShowFilter` `-statSpan <day|week|month>` `-statScrollBottom` `-showExport` `-exportDump`(Documents/export-dump.png) |
|
||||
| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-premiumPreview` `-helpPreview` `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) |
|
||||
| 버블 | `-radialExpanded` `-radialDoubleTapTest`(2초 뒤 FAB 더블탭 재현 — `-settings.radialDoubleTapTab <탭>`과 함께) |
|
||||
| 일기 | `-diarySeed` `-diaryOpenToday`(**`-startTab diary` 필수**) `-diaryPage <N>` `-diaryShowConfig` `-diaryShowExport`(달력 화면 onAppear — `-diaryOpenToday`와 함께 쓰면 안 뜸) `-diaryExportRun pdf|image`(결과를 Documents로 복사) `-diaryShowCalendarPicker` `-diarySeedEvents` `-diaryHideFirstAction` `-diaryShowActionFilter` / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` |
|
||||
| 일기 | `-diarySeed`(필기 획 2줄 포함 — 달력 화면에서 먼저 시드 후 재실행으로 열 것) `-diaryOpenToday`(**`-startTab diary` 필수**) `-diaryPage <N>` `-diaryZoomScale <배>`(2.5초 뒤 fit×N 프로그램 줌 — 획 유지·선명도 확인) `-diaryShowConfig` `-diaryShowExport`(달력 화면 onAppear — `-diaryOpenToday`와 함께 쓰면 안 뜸) `-diaryExportRun pdf|image`(결과를 Documents로 복사) `-diaryShowCalendarPicker` `-diarySeedEvents` `-diaryHideFirstAction` `-diaryShowActionFilter` / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` |
|
||||
| 워치 | `-complicationPreview` `-complicationScroll` `-autoRunFirstAction` |
|
||||
|
||||
---
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -312,6 +312,20 @@ private struct DiaryZoomContainer<Content: View>: UIViewRepresentable {
|
||||
scroll.contentSize = pageSize
|
||||
scroll.zoomedView = hosting.view
|
||||
context.coordinator.hosting = hosting
|
||||
#if DEBUG
|
||||
// 검증용: -diaryZoomScale 2 → 2.5초 뒤 fit 대비 N배로 프로그램 줌
|
||||
// (핀치 없이 확대 시 필기 획 유지·선명도를 스크린샷으로 확인)
|
||||
let zoomFactor = UserDefaults.standard.double(forKey: "diaryZoomScale")
|
||||
if zoomFactor > 0 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { [weak scroll] in
|
||||
guard let scroll else { return }
|
||||
scroll.setZoomScale(scroll.minimumZoomScale * zoomFactor, animated: false)
|
||||
scroll.delegate?.scrollViewDidEndZooming?(
|
||||
scroll, with: scroll.zoomedView, atScale: scroll.zoomScale
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return scroll
|
||||
}
|
||||
|
||||
@ -599,25 +613,20 @@ final class SharpPencilCanvasView: PKCanvasView {
|
||||
Self.apply(targetContentsScale, view: self)
|
||||
}
|
||||
|
||||
/// ⚠️ 뷰의 contentScaleFactor만 조정한다 — 예전처럼 서브'레이어'의 contentsScale을
|
||||
/// 직접 바꾸고 setNeedsDisplay()를 호출하면, PencilKit이 렌더러로 직접 contents를
|
||||
/// 채우는 획 타일 레이어의 픽셀이 지워진 채 다시 그려지지 않아 확대할 때마다
|
||||
/// 필기가 사라지는 버그가 된다 (그리는 동안엔 라이브 렌더로 보였다가 획을 떼면
|
||||
/// 다시 지워짐). 뷰 수준 배율만 지정하면 PencilKit이 자기 타이밍에 새 배율로
|
||||
/// 다시 렌더링하므로 획이 유지되면서 선명해진다.
|
||||
private static func apply(_ scale: CGFloat, view: UIView) {
|
||||
if abs(view.contentScaleFactor - scale) > 0.01 {
|
||||
view.contentScaleFactor = scale
|
||||
}
|
||||
apply(scale, layer: view.layer)
|
||||
for subview in view.subviews {
|
||||
apply(scale, view: subview)
|
||||
}
|
||||
}
|
||||
|
||||
private static func apply(_ scale: CGFloat, layer: CALayer) {
|
||||
if abs(layer.contentsScale - scale) > 0.01 {
|
||||
layer.contentsScale = scale
|
||||
layer.setNeedsDisplay()
|
||||
}
|
||||
for sublayer in layer.sublayers ?? [] {
|
||||
apply(scale, layer: sublayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DiaryPencilCanvas: UIViewRepresentable {
|
||||
|
||||
@ -15,6 +15,9 @@
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import PhotosUI
|
||||
#if DEBUG
|
||||
import PencilKit // 시드 필기 획 생성용 (검증 전용)
|
||||
#endif
|
||||
|
||||
// MARK: - 읽기 전용 모드 (프리미엄 만료 후 열람 허용)
|
||||
|
||||
@ -259,6 +262,25 @@ struct DiaryRootView: View {
|
||||
textBox.centerX = 0.42; textBox.centerY = 0.62; textBox.widthRatio = 0.5
|
||||
textBox.page = page
|
||||
context.insert(textBox)
|
||||
// 펜슬 획 시드 — 확대 시 획이 유지·선명한지 검증용 (물결 곡선 2줄)
|
||||
var strokes: [PKStroke] = []
|
||||
for row in 0..<2 {
|
||||
let baseY = 780 + CGFloat(row) * 90
|
||||
let points = (0...60).map { i -> PKStrokePoint in
|
||||
let t = CGFloat(i) / 60
|
||||
return PKStrokePoint(
|
||||
location: CGPoint(x: 120 + t * 520, y: baseY + sin(t * .pi * 5) * 32),
|
||||
timeOffset: TimeInterval(i) * 0.01,
|
||||
size: CGSize(width: 5, height: 5),
|
||||
opacity: 1, force: 1, azimuth: 0, altitude: .pi / 2
|
||||
)
|
||||
}
|
||||
strokes.append(PKStroke(
|
||||
ink: PKInk(.pen, color: row == 0 ? .black : .systemBlue),
|
||||
path: PKStrokePath(controlPoints: points, creationDate: .now)
|
||||
))
|
||||
}
|
||||
page.drawingData = PKDrawing(strokes: strokes).dataRepresentation()
|
||||
try? context.save()
|
||||
}
|
||||
#endif
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user