mycode/myApp/HaruDanim/IOS/Core/CloudSyncMonitor.swift
songyc macbook fbd4810628 fix: resolve widget sync crashes via local AppGroup container and improve goal widget to show all sub-goals
위젯 동기화 크래시 수정:
- DataStore: CloudKit 미러링을 메인 앱 프로세스 전담으로 분리.
  위젯 등 앱 확장(.appex)은 CloudKit 엔타이틀먼트가 없고 메모리·수명
  제약으로 미러링 설정이 크래시/행을 유발하므로, 확장 프로세스는 항상
  같은 App Group 스토어 파일을 로컬 전용(cloudKitDatabase: .none)으로
  연다. 확장이 쓴 변경은 메인 앱이 히스토리로 집어 CloudKit에 내보냄
- CloudSyncMonitor 신설: 메인 앱이 NSPersistentStoreRemoteChange
  (다른 기기의 CloudKit 가져오기·위젯 인텐트의 쓰기)를 디바운스로 받아
  라이브 액티비티 → 위젯 타임라인 → 워치 스냅숏 순으로 즉시 갱신
- 인터랙티브 위젯(AppIntent)의 컨테이너 매핑 점검: IntentStore.commit이
  저장 직후 reloadAllTimelines를 이미 보장함을 확인·문서화

모음 탭 목표 진행 현황 카드 개선:
- '다짐별로 보기'의 상위 3개 제한 제거 — 모든 다짐 확인 가능
- 화면 균형을 위해 접힘/펼침 UX 적용: iPhone은 3개, iPad는 6개까지
  접힌 상태로 보여주고 '다짐 N개 더 보기' 버튼으로 카드 안에서 펼침
  (카드 탭 이동과 충돌하지 않는 독립 버튼, 스냅 애니메이션)
- DebugSeed: 접힘/펼침 검증용 4번째 다짐 추가, -expandGoalCard 인자 추가

검증: iPhone 17 Pro·iPad Pro 11 시뮬레이터에서 접힘/펼침 렌더링과
사이드바 레이아웃 확인, 전체 타깃(iOS·워치·위젯) 빌드 성공

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-11 21:35:20 +09:00

50 lines
1.9 KiB
Swift

//
// CloudSyncMonitor.swift
// Haru_Danim
//
// CloudKit ( )
// - SwiftData CloudKit · ( )
// NSPersistentStoreRemoteChange . · ·
// , .
// - .
//
import CoreData
import SwiftData
import WidgetKit
@MainActor
final class CloudSyncMonitor {
static let shared = CloudSyncMonitor()
private var observer: (any NSObjectProtocol)?
private var refreshTask: Task<Void, Never>?
/// 1 . .
func start(container: ModelContainer) {
guard observer == nil else { return }
observer = NotificationCenter.default.addObserver(
forName: .NSPersistentStoreRemoteChange,
object: nil,
queue: nil
) { _ in
Task { @MainActor in
CloudSyncMonitor.shared.scheduleRefresh(container: container)
}
}
}
private func scheduleRefresh(container: ModelContainer) {
refreshTask?.cancel()
refreshTask = Task { @MainActor in
// CloudKit
try? await Task.sleep(for: .seconds(2))
guard !Task.isCancelled else { return }
// /
LiveActivityManager.sync(context: container.mainContext)
WidgetCenter.shared.reloadAllTimelines()
WatchSyncManager.shared.pushSnapshot()
}
}
}