You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WatchKit complication不更新求助:iPhone改日期后表盘组件未同步

问题分析与解决方案:iPhone与Watch Complication日期同步问题

需求概述

  • iPhone端可修改目标日期
  • Apple Watch的complication组件显示距离目标日期的剩余天数
  • iPhone修改日期后,Watch端数据同步并持久化

当前实现

在Watch应用和complication中使用同一个OneDayState单例(@ObservedObject)展示数据:

@ObservedObject state = OneDayState.shared

...
Text( state.daysUntilValue )

实际问题

  • Watch应用活跃运行时:
    • 应用内数据正常更新
    • 返回主屏后,complication仍显示旧值
    • 重启Watch后,complication显示正确值
  • Watch应用未活跃时:
    • complication和Watch应用均无法获取新值

期望效果

  • 即使Watch应用未运行,修改iPhone端日期后也能同步新值
  • complication在iPhone修改日期后立即更新

提供的OneDayState代码

class OneDayState : NSObject, ObservableObject, WCSessionDelegate
{
    
    static let shared = OneDayState()
    
    // connection to the settings
    let session = WCSession.default

    // connection to the user defaults
    let settings =  UserDefaults(suiteName: "[removed]")!;

    // what is watched by the UI
    var daysUntilValue : String {
        return  String( Calendar.current.dateComponents( [.day], from: .now, to: theDate).day!)
    }
    
    // the target date
    @Published var theDate : Date = Date.now

    // setup this
    override init()
    {
        super.init()
        session.delegate = self
        session.activate()
        theDate = settings.object(forKey: "target" ) as? Date ?? Date.now;
    }

    // you seem to have to override this
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("sesison activated")
    }

    // when the application context changes, we just store the new date
    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any])
    {
        let newDate = applicationContext["target"] as? Date ?? Date.now;
        DispatchQueue.main.async
        {
            self.settings.set( newDate, forKey: "target")
            self.theDate = newDate;
        }
    }
        
}

错误点分析与解决方案

核心问题1:WCSession仅在Watch应用活跃时接收数据

WCSession的didReceiveApplicationContext只有当Watch应用处于前台活跃状态时才会触发。如果Watch应用未运行或在后台,无法接收到iPhone发送的上下文更新,自然无法同步数据到UserDefaults,complication也无法获取新值。

核心问题2:Complication未监听UserDefaults变化

当前complication依赖OneDayState的@Published属性更新,但OneDayState单例只有在Watch应用启动时才会初始化。如果Watch应用未运行,单例不会被创建,complication无法感知UserDefaults的变化;即使应用在后台,complication也不会主动监听UserDefaults的更新。

核心问题3:Complication未设置主动刷新机制

Complication需要主动从共享UserDefaults读取最新值,而不是仅依赖应用内的ObservableObject更新。同时,需要让iPhone端发送数据时触发Watch complication的刷新。


具体修复步骤

1. 优化iPhone端数据发送逻辑

iPhone端发送目标日期时,不仅要更新ApplicationContext,还要处理Watch不可达的情况,同时触发Watch后台刷新:

// iPhone端代码示例
let sharedDefaults = UserDefaults(suiteName: "[你的suiteName]")!
sharedDefaults.set(targetDate, forKey: "target")

if WCSession.default.isReachable {
    do {
        try WCSession.default.updateApplicationContext(["target": targetDate])
    } catch {
        print("发送上下文失败: \(error)")
    }
} else {
    // 发送离线数据,Watch后台接收后更新
    WCSession.default.transferUserInfo(["target": targetDate])
}

2. 修改Watch端OneDayState,支持后台接收数据

实现WCSessionDelegate的didReceiveUserInfo方法,处理iPhone离线发送的数据,并触发complication刷新:

func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any]) {
    let newDate = userInfo["target"] as? Date ?? Date.now
    DispatchQueue.main.async {
        self.settings.set(newDate, forKey: "target")
        self.theDate = newDate
        // 通知所有complication刷新
        CLKComplicationServer.sharedInstance().reloadComplications()
    }
}

3. Complication直接从共享UserDefaults读取数据

不要依赖OneDayState单例,complication数据源直接读取UserDefaults,并配置刷新规则:

// Complication数据源类
class ComplicationDataSource: NSObject, CLKComplicationDataSource {
    let sharedDefaults = UserDefaults(suiteName: "[你的suiteName]")!
    
    func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
        let targetDate = sharedDefaults.object(forKey: "target") as? Date ?? Date.now
        let daysUntil = Calendar.current.dateComponents([.day], from: .now, to: targetDate).day ?? 0
        let textProvider = CLKSimpleTextProvider(text: "\(daysUntil)天")
        let template = CLKComplicationTemplateModularLargeStandardBody()
        template.headerTextProvider = CLKSimpleTextProvider(text: "剩余天数")
        template.bodyTextProvider = textProvider
        let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
        handler(entry)
    }
    
    // 设置complication支持向前时间旅行
    func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
        handler([.forward])
    }
    
    // 设置刷新策略,支持应用启动时刷新
    func getRefreshPolicy(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationRefreshPolicy) -> Void) {
        handler(.onApplicationLaunch)
    }
}

4. 配置Watch App的Background Modes

在Watch App的Capabilities中开启Background Modes,勾选Background fetch和Remote notifications,确保Watch能在后台接收数据并刷新complication。

5. 监听UserDefaults变化,同步到@Published属性

在OneDayState中添加监听,确保应用在后台时也能感知数据更新:

override init() {
    super.init()
    session.delegate = self
    session.activate()
    theDate = settings.object(forKey: "target") as? Date ?? Date.now
    // 监听共享UserDefaults变化
    NotificationCenter.default.addObserver(self, selector: #selector(defaultsChanged), name: UserDefaults.didChangeNotification, object: settings)
}

@objc func defaultsChanged() {
    if let newDate = settings.object(forKey: "target") as? Date {
        DispatchQueue.main.async {
            self.theDate = newDate
        }
    }
}

// 销毁时移除观察者
deinit {
    NotificationCenter.default.removeObserver(self)
}

内容的提问来源于stack exchange,提问作者Darren Oakey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 12:21:12