WatchKit complication not updating

393 views Asked by At

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

There are 3 answers

0
Owen Zhao On BEST ANSWER

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.

0
Reinhard Männer On

Before SwiftUI I had a dependent Watch app that required an iOS companion app (my current Watch app is independent). Thus I can only say what I did at that time to make it work.
I had a WatchSessionManager that received in func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any]) complication data sent by func transferCurrentComplicationUserInfoIfPossible(userInfo: [String: AnyObject]).
The problem then was how to transfer the received complication data to a ComplicationController: NSObject, CLKComplicationDataSource. I then used the NotificationCenter to post a userInfo with the data. The ComplicationController received the post and reloaded the time line for the active complications. This way the complications were always updated, even when the Watch extension was not active.
To update the Watch app itself, even when it was not active, I transferred the data using func transferUserInfo(_ userInfo: [String: Any]) -> WCSessionUserInfoTransfer?. If the Watch extension was not active, the userInfo was received as soon as the Watch extension become active. In any case, the Watch extension could use the data to update its state as required.
I am not sure how to translate this to SwiftUI. I am using now the new WatchOS 9 widgets, but they work differently.

0
Cheezzhead On

Your observable object looks fine. I would need to see more of the complication's code to be sure, but based on your code snippet it looks like something is going wrong with observing the OneDayState object.

@ObservedObject var state = OneDayState.shared

Instantiating the object in its type declaration like this isn't the way to go, because the object will be recreated whenever the complication or view is recalculated. You should either use @StateObject (which persists the variable even after the complication is invalidated), or inject the object into the environment using .environmentObject(_:). I would also avoid using a singleton class as observable object (using .shared). Just create it inside the complication, where it's needed.

@StateObject var state = OneDayState()