Search code examples
iosswiftuiwatchkitwatchconnectivityapple-watch-complication

WatchKit complication not updating


I want to do something that seems simple - I have an app on the iPhone that allows you to change a date. On the watch, I have a complication that shows among other things the days until that date. So clearly, when you change the date on the iPhone app, I want the date on the watch to change and that state to be persisted until or if you change the date on the iPhone again.

What I've done is created a state object included in both the complication and the watch app, and in both I just do this to display the value

    @ObservedObject state = OneDayState.shared

    ...
    Text( state.daysUntilValue )

what is happening when I change the date on the iphone:

  • if the watch app is active and running
    • the date displayed on the app changes like it should
    • if I go back to the home screen, the complication has the old bad value
    • if I reset the watch - the complication now has the correct value
  • if the watch app is not active and running
    • neither the complication nor the watch gets the new value

What I want to happen

  • the app to get the new value even if it's not running when I change the value on the iphone
  • the complication to change instantly when I change the value on the iPhone

Here is the code of my state object - what am I doing wrong?? (thx)

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;
        }
    }
        
}

Solution

  • When your watch app is running in background, you should use WKWatchConnectivityRefreshBackgroundTask to get the data from iPhone.

    WKWatchConnectivityRefreshBackgroundTask

    Currently WKWatchConnectivityRefreshBackgroundTask, so you watch won't get the data until it goes foreground.

    Then you need to manually update your complication yourself.