Search code examples
ioscore-datawatchkitwatchos-2watchconnectivity

How To Share Data with Watch OS 2 to display in WKInterfaceTable when working with CoreData


I am using WatchConnectivity to try to send data of type NSManagedObject called arrayOfOjects to the Watch. Each object has a string property called title.

The InterfaceController on the Watch loads and displays and empty table - as intended because the array is empty, then when the user requests the data it is sent using the didReceiveMessage method on the phone.

I am unsure how to add the dictionary array to the objectsArray to display in the WKInterfaceTable.

Does anyone know how I can send the data to the watch to display in the table to make changes and sync them back with the phone ?

Apple Watch:

class ObjectsInterfaceController: WKInterfaceController, WCSessionDelegate {

var session : WCSession!
var objectsArray = [[AnyObject]]()

@IBOutlet var table: WKInterfaceTable!
@IBOutlet var titleLabel: WKInterfaceLabel!

func loadTableData() {

    table.setNumberOfRows(self.objectsArray.count, withRowType: "CellRow")
    if self.objectsArray.count > 0 {
        for (index, name) in self.objectsArray.enumerate() {
            let row = self.table.rowControllerAtIndex(index) as! CellRowController
            row.objectCellLabel.setText(name.title)
        }
    }
}

override init() {
    super.init()
    loadTableData()
}

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    // Interface Objects
}

override func willActivate() {
    // This method is called when watch view controller is about to be visible to user
    super.willActivate()
    //Check if session is supported and Activate
    if (WCSession.isSupported()) {
        session = WCSession.defaultSession()
        session.delegate = self
        session.activateSession()
    }
}

//Swift
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {

    let value = message["Value"]

    dispatch_async(dispatch_get_main_queue()) {
        self.objectsArray.removeAll()
        self.objectsArray.append(value! as! Array)
        self.loadTableData()
    }

    //send a reply
    replyHandler(["Value":"Yes"])

}
}

iPhone

I already fetch all the objects and store in array.

var objectsArray = [Objects]()

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {

    //send a reply
    replyHandler(["Value": [objectsArray]])

}

I need to be able to modify the properties of the objects and save the changes on the iPhone but atm I cannot even send the data and display in the table :( I have been able to send simple string values within a dictionary between devices but not arrays or actual data.


Solution

  • TL/DR:

    You can only send basic types (such as strings, integers, doubles) to your watch. This question has more details about sending custom objects.

    The other issue:

    Even if you archived or serialized the managed objects, it's still not possible to send that particular data from the phone to the watch.

    A NSManagedObject is only valid within its own context. In this case, the managed object are registered with a specific NSMangedObjectContext on your iOS app. The managed object is not useful apart from its context, and its managed object context doesn't exist anywhere else but the phone.

    NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.

    Since it's not possible pass a managed object from one context (or thread or queue) to another on the same platform, you definitely can't pass a managed object between the phone and its paired watch.

    What can you do?

    • If you had a way to share your Core Data store between the phone and the watch, you could convert the managed object IDs to strings (using URIRepresentation), then pass those strings to the watch, then convert those strings back to object IDs and fetch the corresponding objects. This is explained in detail in this question.

      However, app groups are no longer supported on watchOS 2, and it would be very complex to keep two different stores in sync across devices.

    • A much lighter solution is to pass details about the title, keep track of whatever changes you make on the watch, then send back a dictionary of titles that were inserted, deleted, or otherwise changed.

      The phone would then update the managed objects corresponding to those changed titles.

      This is similar to how NSFetchedResultsControllerDelegate responds to changes to keep its results in sync.

    Does anyone know how I can send the data to the watch to display in the table to make changes and sync them back with the phone?

    I gave you a general overview. Anything more detailed would be far too broad to cover in this answer. All I can suggest is that developers either used a third-party framework, or wrote their own implementation.

    Some considerations:

    Just keep in mind that you don't want to degrade the user's watch experience by transferring large amounts of data back and forth. Keep your watch app lightweight and responsive, as it's ideally only designed to be used for a few seconds.

    If you can simplify your watch app design (e.g., only marking a todo list item as completed), you can eliminate much of the "sync" overhead, and delegate the more complex tasks to the iOS app.