SimpleWatchConnectivity sample - modernizing it

Dear Experts,

I have been looking at thr SimpleWatchConnectivity sample code:

https://developer.apple.com/documentation/watchconnectivity/transferring-data-with-watch-connectivity

There are a couple of things in there that look out of date. Firstly, it uses a WKApplicationDelegate to receive the background tasks. I believe this can probably be entirely removed, and replaced with .backgroundTask(.watchConnectivity) { ... } on the App. Is that true? What do I need something inside the { ... } there?

Secondly, it is using NSNotificationCenter to send received data from the WCSessionDelegate to the SwiftUI view hierarchy. Is there a better way to do that? I have spent a while trying to work out how a WCSessionDelegate class can connect to a binding to a SwiftUI @State property, and cause the UI to update in response to received data, but I haven't made it work.

Are there any newer examples of how to do this? I'm currently only trying to send some simple applicationContext state from the phone to the watch and have some views update to show the latest values.

Thanks, Phil.

Answered by Frameworks Engineer in 817687022

Hi Phil!

That is the latest example. If your goal is to exchange information using applicationContext, probably the most straightforward way to do this is:

  1. Create an Observable object to store that data.
  2. Make your Observable object accessible to your WCSessionDelegate and update it when you receive updated applicationContext values.
  3. Store the Observable object in the Environment. Something like:
@main
struct MyWatchApp: App {
    // You'll have other things here
    private var applicationContextModel = ApplicationContextModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(applicationContextModel)
        }
    }
}
  1. Then use it in your view. Something like:
@Environment(ApplicationContextModel.self) private var applicationContextModel

var body: some View {
    VStack {
        Text("\(applicationContextModel.myValue)")
    }
}

Hi Phil!

That is the latest example. If your goal is to exchange information using applicationContext, probably the most straightforward way to do this is:

  1. Create an Observable object to store that data.
  2. Make your Observable object accessible to your WCSessionDelegate and update it when you receive updated applicationContext values.
  3. Store the Observable object in the Environment. Something like:
@main
struct MyWatchApp: App {
    // You'll have other things here
    private var applicationContextModel = ApplicationContextModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(applicationContextModel)
        }
    }
}
  1. Then use it in your view. Something like:
@Environment(ApplicationContextModel.self) private var applicationContextModel

var body: some View {
    VStack {
        Text("\(applicationContextModel.myValue)")
    }
}

Many thanks! Here is the skeleton of what I have; any comments? In particular, should I be doing anything in the .backgroundTask(.watchConnectivity)?

import Foundation;
import SwiftUI;
import WatchConnectivity;

@Observable
class Stats
{
    var data: [String: Any] = [:];
};


struct StatsView: View
{
    @Environment(Stats.self) private var stats;

    var body: some View {
        VStack {
            Text(stats.data.description)
        }
    }
};


class SessionDelegate: NSObject, WCSessionDelegate
{
    private var stats: Stats;

    init(stats: Stats)
    {
        self.stats = stats;
    }
    
    func session(_ session: WCSession,
                 activationDidCompleteWith activationState: WCSessionActivationState,
                 error: (any Error)?) {
        // TODO check state, error, etc.
        stats.data = session.receivedApplicationContext;
    }
    
    func session(_ session: WCSession,
                 didReceiveApplicationContext application_context: [String: Any])
    {
        stats.data = session.receivedApplicationContext;
    }
    
};


@main
struct MyWatchApp: App
{
    private var stats: Stats = Stats();
    private var session_delegate: SessionDelegate;
    
    init()
    {
        assert(WCSession.isSupported(), "WCSession is not supported, hmm, when does this happen?");
        
        session_delegate = SessionDelegate(stats: stats);
        WCSession.default.delegate = session_delegate;
        WCSession.default.activate();
    }

    var body: some Scene {
        WindowGroup {
            StatsView()
                .environment(stats)
        }
        .backgroundTask(.watchConnectivity) {
        }
    }
};

I think you'd need to schedule a snapshot refresh if the WatchConnectivity background task updates your UI

Or maybe not, according to the docs at https://developer.apple.com/documentation/watchkit/preparing-to-take-your-watchos-app-s-snapshot

If your app uses the SwiftUI BackgroundTask tasks, the system automatically detects any changes to the user interface and schedules the snapshot task.

SimpleWatchConnectivity sample - modernizing it
 
 
Q