fix(watch): 컴플리케이션이 워치 앱을 열지 않아도 갱신되도록 수정
- 워치 앱 시작(App.init) 시점에 WCSession 활성화: 백그라운드로 깨어나 스냅숏을 받을 수 있도록 델리게이트를 미리 설정 - iPhone이 transferCurrentComplicationUserInfo 전용 채널로도 스냅숏 전송 (전송 예산 고려: 측정 상태 변화 또는 30분 경과 시에만) - 워치 didReceiveUserInfo 수신 → 캐시 저장 + 컴플리케이션 타임라인 갱신 - ComplicationStore.defaults 단일 인스턴스화 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
parent
e5550eb106
commit
0cdb566ad5
@ -9,6 +9,12 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct Haru_DanimWatch_Watch_AppApp: App {
|
||||
init() {
|
||||
// 컴플리케이션용 스냅숏은 워치 앱이 백그라운드로 깨어나 받는다.
|
||||
// 그 시점에 델리게이트가 있어야 하므로 화면 표시 전에 세션을 활성화한다
|
||||
WatchStore.shared.activate()
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
|
||||
@ -113,6 +113,16 @@ extension WatchStore: WCSessionDelegate {
|
||||
WatchStore.shared.apply(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
/// iPhone이 컴플리케이션 갱신용 전용 채널로 미는 스냅숏.
|
||||
/// 워치 앱이 꺼져 있어도 백그라운드로 깨어나 수신 → 캐시 + 타임라인 갱신(apply)
|
||||
nonisolated func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
|
||||
guard let data = userInfo[WatchSync.snapshotKey] as? Data,
|
||||
let decoded = WatchSnapshot.decode(data) else { return }
|
||||
Task { @MainActor in
|
||||
WatchStore.shared.apply(decoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 색 변환 (워치 타깃 전용)
|
||||
|
||||
@ -15,9 +15,7 @@ import WidgetKit
|
||||
// MARK: - 스냅숏 로드
|
||||
|
||||
nonisolated enum ComplicationStore {
|
||||
static var defaults: UserDefaults {
|
||||
UserDefaults(suiteName: "group.com.yechan.HaruDanim") ?? .standard
|
||||
}
|
||||
static let defaults: UserDefaults = UserDefaults(suiteName: "group.com.yechan.HaruDanim") ?? .standard
|
||||
|
||||
static func snapshot() -> WatchSnapshot? {
|
||||
guard let data = defaults.data(forKey: WatchSync.cachedSnapshotKey) else { return nil }
|
||||
|
||||
@ -113,13 +113,33 @@ final class WatchSyncManager: NSObject {
|
||||
return snapshot
|
||||
}
|
||||
|
||||
/// 컴플리케이션 전용 전송의 최근 상태 (하루 전송 예산이 있어 절약해서 보냄)
|
||||
private var lastComplicationStateKey = ""
|
||||
private var lastComplicationPushAt = Date.distantPast
|
||||
|
||||
/// 최신 스냅숏을 워치로 전송 (데이터 변경 시마다 호출)
|
||||
func pushSnapshot() {
|
||||
guard WCSession.isSupported() else { return }
|
||||
let session = WCSession.default
|
||||
guard session.activationState == .activated, session.isPaired, session.isWatchAppInstalled else { return }
|
||||
guard let data = makeSnapshot().encoded() else { return }
|
||||
let snapshot = makeSnapshot()
|
||||
guard let data = snapshot.encoded() else { return }
|
||||
// applicationContext: 워치 앱이 다음에 열릴 때 확실히 전달되는 기본 채널
|
||||
try? session.updateApplicationContext([WatchSync.snapshotKey: data])
|
||||
pushForComplicationIfNeeded(session: session, snapshot: snapshot, data: data)
|
||||
}
|
||||
|
||||
/// 워치 앱이 꺼져 있어도 컴플리케이션이 갱신되도록 전용 채널로 전송.
|
||||
/// 시스템이 하루 전송 횟수를 제한하므로 측정 상태가 바뀌었거나 30분이 지났을 때만 보낸다
|
||||
private func pushForComplicationIfNeeded(session: WCSession, snapshot: WatchSnapshot, data: Data) {
|
||||
guard session.isComplicationEnabled else { return }
|
||||
let stateKey = "\(snapshot.runningName ?? "")|\(snapshot.extraRunningCount)|\(snapshot.isPremium)"
|
||||
let now = Date.now
|
||||
guard stateKey != lastComplicationStateKey
|
||||
|| now.timeIntervalSince(lastComplicationPushAt) > 30 * 60 else { return }
|
||||
lastComplicationStateKey = stateKey
|
||||
lastComplicationPushAt = now
|
||||
session.transferCurrentComplicationUserInfo([WatchSync.snapshotKey: data])
|
||||
}
|
||||
|
||||
// MARK: 명령 처리
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user