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
|
@main
|
||||||
struct Haru_DanimWatch_Watch_AppApp: App {
|
struct Haru_DanimWatch_Watch_AppApp: App {
|
||||||
|
init() {
|
||||||
|
// 컴플리케이션용 스냅숏은 워치 앱이 백그라운드로 깨어나 받는다.
|
||||||
|
// 그 시점에 델리게이트가 있어야 하므로 화면 표시 전에 세션을 활성화한다
|
||||||
|
WatchStore.shared.activate()
|
||||||
|
}
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
|||||||
@ -113,6 +113,16 @@ extension WatchStore: WCSessionDelegate {
|
|||||||
WatchStore.shared.apply(decoded)
|
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: - 색 변환 (워치 타깃 전용)
|
// MARK: - 색 변환 (워치 타깃 전용)
|
||||||
|
|||||||
@ -15,9 +15,7 @@ import WidgetKit
|
|||||||
// MARK: - 스냅숏 로드
|
// MARK: - 스냅숏 로드
|
||||||
|
|
||||||
nonisolated enum ComplicationStore {
|
nonisolated enum ComplicationStore {
|
||||||
static var defaults: UserDefaults {
|
static let defaults: UserDefaults = UserDefaults(suiteName: "group.com.yechan.HaruDanim") ?? .standard
|
||||||
UserDefaults(suiteName: "group.com.yechan.HaruDanim") ?? .standard
|
|
||||||
}
|
|
||||||
|
|
||||||
static func snapshot() -> WatchSnapshot? {
|
static func snapshot() -> WatchSnapshot? {
|
||||||
guard let data = defaults.data(forKey: WatchSync.cachedSnapshotKey) else { return nil }
|
guard let data = defaults.data(forKey: WatchSync.cachedSnapshotKey) else { return nil }
|
||||||
|
|||||||
@ -113,13 +113,33 @@ final class WatchSyncManager: NSObject {
|
|||||||
return snapshot
|
return snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 컴플리케이션 전용 전송의 최근 상태 (하루 전송 예산이 있어 절약해서 보냄)
|
||||||
|
private var lastComplicationStateKey = ""
|
||||||
|
private var lastComplicationPushAt = Date.distantPast
|
||||||
|
|
||||||
/// 최신 스냅숏을 워치로 전송 (데이터 변경 시마다 호출)
|
/// 최신 스냅숏을 워치로 전송 (데이터 변경 시마다 호출)
|
||||||
func pushSnapshot() {
|
func pushSnapshot() {
|
||||||
guard WCSession.isSupported() else { return }
|
guard WCSession.isSupported() else { return }
|
||||||
let session = WCSession.default
|
let session = WCSession.default
|
||||||
guard session.activationState == .activated, session.isPaired, session.isWatchAppInstalled else { return }
|
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])
|
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: 명령 처리
|
// MARK: 명령 처리
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user