For anyone coming across this thread, here's how I got it to work. It might not be 100% correct, but it's definitely working here.
Background:
iOS app in Objective-C with a SwiftUI Watch extension ("Watch App").
Watch App has a delegate ("WatchAppDelegate") conforming to WKApplicationDelegate and WCSessionDelegate.
WatchConnectivity session delegate methods are in the WatchAppDelegate.
WatchAppDelegate also contains my ModelData class. This contains some @Published vars that other Views can see, and will refresh when those values change. The class is also an @ObservableObject.
WatchAppDelegate.swift
// Instantiate the model data here, *once only*. This is your single source of truth.
let modelData: ModelData = ModelData()
class ModelData : ObservableObject {
@Published var availableItems: [ItemDetail] = defaultsGetAvailableItems() // Gets an array of ItemDetail from the defaults
@Published var mainItem: ItemDetail = defaultsGetMainItem() // Again, from the defaults
}
class WatchAppDelegate : NSObject, WKApplicationDelegate, WCSessionDelegate, ObservableObject {
var session: WCSession?
override init() {
super.init()
if(WCSession.isSupported()) {
if(session == nil) {
session = WCSession.default // Start the Watch Connectivity session
session!.delegate = self
session!.activate()
}
}
}
// Delegate methods go here, session didReceiveMessage etc.
...
}
When the WCSession delegate methods are called and triggered in WatchAppDelegate, the delegate deals with the new data that's come in. So, for example, the iOS app has just sent a message dictionary with, maybe: "newItems" : arrayOfNewItems. The delegate takes those new items and stores them in the defaults.
At that point, you also update the modelData: modelData.availableItems = defaultsGetAvailableItems() and modelData.mainItem = defaultsGetMainItem(). Your one source of truth has been updated with the new data.
Okay, the WatchApp is the @main entry point into the app, and you need to start the delegate in there:
WatchApp.swift
@main
struct WatchApp: App {
@WKApplicationDelegateAdaptor private var appDelegate: WatchAppDelegate // Launches the delegate, which creates the modelData instance
var body: some Scene {
WindowGroup {
AppContentView(modelData: modelData)
.environmentObject(modelData) // Give access to modelData to the receiving view (probably, I dunno, but it's necessary)
}
}
}
struct AppContentView : View {
@ObservedObject var modelData: ModelData
var body: some View {
VStack {
if(modelData.availableitems.count == 0) { // If there are no items, display an appropriate view and message
NoItemsView()
.environmentObject(modelData)
} else { // We have items
ItemsListView()
.environmentObject(modelData)
}
...
}
ItemsListView.swift
struct ItemsListView: View {
@EnvironmentObject var modelData: ModelData // To access the data
var body: some View {
VStack {
NavigationView {
List {
// List of items
ForEach(modelData.availableItems) { item in
NavigationLink {
ItemDetailView(item: item)
} label: {
TableRowView(item: item)
}
}
}
}
}
}
}
ItemDetailView.swift
struct ItemDetailView: View {
@EnvironmentObject var modelData: ModelData
var event: ItemDetail
// This lets us access the particular item in the array by its index
var itemIndex: Int {
modelData.availableItems.firstIndex(where: { $0.id == item.id })!
}
var body: some View {
...
Happy to be told what I've done is wrong or bad or bad practice or whatever, but it's working right now...!