I’ll answer your questions in 3 sections: 1. DataModels and SwiftUI, 2. Watch Connectivity and 3. Saving and synchronising data.
DataModels and SwiftUI
A key concept of SwiftUI is that the user interface (SwiftUI Views) only interacts with the user, displaying text etc and collecting input (e.g. button presses). All other app processing (e.g. data manipulation, data storage and retrieval) is done elsewhere in an app-specific class (typically called “DataModel”) created by the developer. Your app essentially has two parts: the watch app and the phone app. Each needs a DataModel - “WatchDataModel” and “PhoneDataModel” - being names that I decided. The tasks of the WatchDataModel are to 1. Collect and aggregate (?) titles and counts, 2. Transmit those to the phone 3. Maintain a local copy of the collected data 4. Prepare data for displaying to the user. The PhoneDataModel has to 1. Receive aggregated data from the watch 2. Store those data locally, synchronising as required with existing data (?) 3. Prepare data for displaying to the user, i.e. creating a property that a SwiftUI View can refer to for displaying on the screen.
In my sample code, each data model is created as a singleton - meaning that there is only ever one copy created, no matter how many times it gets referred to elsewhere within your app.
class WatchDataModel : NSObject, WCSessionDelegate, ObservableObject {
static let shared = WatchDataModel() // when referring to WatchDataModel.shared only one version is ever created
let session = WCSession.default // this essentially creates a shortcut to WCSession.default
Watch Connectivity
Within each data model, the connectivity to/from the watch is provided by the WatchConnectivity framework and the DataModel’s class has to adopt the WCSessionDelegate protocol for this to work.
import WatchConnectivity // the Apple code to control the watch-phone connection
class WatchDataModel : NSObject, WCSessionDelegate, ObservableObject { // WCSessionDelegate allows this class to be "delegated" certain tasks from WCSession
When your app starts up (Watch or Phone), it needs to initialise its data model, which then needs to tell the connectivity controller (WCSession.default) to delegate certain tasks (e.g. in PhoneDataModel, deal with incoming data) to the data model. You can refer to this controller directly as WCSession.default, but typically one creates a static property let session = WCSession.default and then refer to that property.
Before sending / receiving, you have to activate a session (in fact, you should really check that a session is active before attempting to send anything).
if WCSession.isSupported() { // checks if this device is capable of watch-phone communication (e.g. an iPad is not)
session.delegate = self // allows this DataModel (class) to process tasks delegated (assigned) to it by WCSession
session.activate() // activates a session so that communication can occur
} else {
print("ERROR: Watch session not supported")
}
As I’ve said previously, there’s a number of ways of sending / receiving data: the choice depends on the volume and type of data, and the urgency of the communication. The one I usually use is transferUserInfo, because of its reliability under various situations.
transferUserInfo sends a dictionary of property list values e.g. “Title”:”Hello World” or “Count” : 21 - the first part is the key and the second is the actual value. But there are various ways of constructing a Dictionary, especially with multiple values. Ref https://developer.apple.com/documentation/swift/dictionary. I don’t know enough about the purpose of your app and the volume / complexity of the data for me to make any suggestion, apart from my previous idea of a struct:
struct CountData {
var title : String = ""
var tCounts = [Int]() // an array of counts
var creationDate = Date()
}
data.session.transferUserInfo(["CountData":data.countData]) // in the watch's ContentView we send a CountData struct (in data.countData) with a key of "CountData" via transferUserInfo to the phone
Importantly, when your phone app receives the dictionary, you must be able to access the dictionary values and do whatever you need to do.
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any]) {
// WCSession has delegated to this PhoneDataModel the task of receiving and processing user info
guard let countData = userInfo["CountData"] as? CountData else { // check that what has been received is the info that was sent. In this example a struct we defined, CountData containing title, counts and date, was sent with a key "CountData"
print("ERROR: unknown data received from Watch")
return
}
// now do something with the received CountData
}
Saving and Synchronising Data
Having received the watch data on the phone via didReceiveUserInfo you now have to do something with it. But what?
I can think of at least 6 different ways of saving and synchronising your data (i.e. making sure the watch and phone have identical sets of data). But, again, I don’t know enough about the purpose of your app and the volume / complexity of the data for me to make any recommendation. Your idea of UserDefaults could be problematic because of your data characteristics and the limits of UserDefaults. Ref https://developer.apple.com/documentation/foundation/userdefaults
I normally use SQLIte instead of CoreData, but that's only because I'd been using SQL databases for decades before I came to Apple development and I find SQL's queries, views, joins and indexes far more flexible - and within my knowledge base.
I'll have to finish here: I'm tired (: Regards, Michaela