How to Populate Table Rows, Using a [String] Array Sent from iPhone by Watch Connectivity

How to populate table rows, using a [String] array sent from iPhone by Watch Connectivity?

You started the question with the statement, "Basically, It's passing the array fine" so it's not a Watch Connectivity issue.

You simply didn't specify the number of rows for choiceTable before iterating through your array.

choiceTable.setNumberOfRows(custArray.count, withRowType: "ChoiceTableRowController")

Determining the problem from the console output:

  • Error - attempt to ask for row 0. Valid range is 0..0

    rowControllerAtIndex: returns (an Optional which is) nil when its index is out of bounds.

    The row controller object, or nil if there are no row controllers yet or index is out of bounds.

    You tried to access a row which didn't exist, leading to a bounds warning.

  • fatal error: unexpectedly found nil while unwrapping an Optional value

    row2.choiceLabel.setText(thName)

    row2 is nil.

You should be able to easily track down bugs like this in the debugger. If you examined row2 and saw that it was nil, you'd realize that the problem wasn't with the array itself, but that the table has no rows.

WatchConnectivity, How to Share an Array between iPhone and Apple Watch

In WKInterfaceController:

I agree with the comment from Jens Peter that you shouldn't wrap didReceiveApplicationContext in the load() function.

Handle errors in activationDidCompleteWith as such:

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("Activation failed with error: \(error.localizedDescription)")
return
}
print("Watch activated with state: \(activationState.rawValue)")
}

That will also help determine if WatchConnectivity is even becoming established.

Remove var watchSession: WCSession? completely and declare WCSession in awake(withContext) as:

if WCSession.isSupported() {
let watchSession = WCSession.default
watchSession = self
watchSession()
}

Also remove the call to load() in awake(withContext).

In InterfaceController it's helpful to put some print statements in your WCSession stubs even if you're not entirely using them. You can see if WatchConnectivity is activating.

func sessionDidBecomeInactive(_ session: WCSession) {
// To support multiple watches.
print("WC Session did become inactive.")
}

func sessionDidDeactivate(_ session: WCSession) {
// To support multiple watches.
WCSession.default.activate()
print("WC Session did deactivate.")
}

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: \(error.localizedDescription)")
return
}
print("Phone activated with state: \(activationState.rawValue)")
}

This line WCSession.default.activate() as you see in sessionDidDeactivate will allow for multiple watch support in the off case the user happens to have more than one watch.

Remove the var watchSession: WCSession? as you did in WKInterfaceController. Once again in viewDidLoad() check for WCSession support with:

if WCSession.isSupported() {
let watchSession = WCSession.default
watchSession = self
watchSession()
}

Update sendToWatch() to:

func sendToWatch() {
let session = WCSession.default()

if session.activationState == .activated {
let appDictionary = ["Array": initalArray]
do {
try session.updateApplicationContext(appDictionary)
} catch {
print(error)
}
}
}

I think that cleans it up a bit and will hopefully work or at least move you forward. I didn't build this in a project however. Hope this helps.

How to send Array to Watch using sendMessage | Error: Could not cast value of type '__NSCFArray' to 'NSString'

The sendMessage method should handle the reply from the phone. And their is no reason to use a didRecieveMessage method on the watch if the iPhone does not use a sendMessage method.

@IBAction func fetchData() {

let messageToSend = ["Value":"Hello iPhone"]
session.sendMessage(messageToSend, replyHandler: { replyMessage in

if let value = replyMessage["Value"] {
self.objectTitlesArray = value as! [String]
self.loadTableData()
}

}, errorHandler: {error in
// catch any errors here
print(error)
})

}

how to send through multiple dictionaries/ multiple 'application updates' with swift 2.2 watch connectivity

You can't send the items separately, as updateApplicationContext would have replaced any earlier application context data with the most recent data. This is briefly mentioned in two different spots in the documentation:

This method overwrites the previous data dictionary, ...

This method replaces the previous dictionary that was set, ...

Naturally, Apple optimizes the whole process for energy/memory efficiency. In this case, if the earlier application context data was still in the queue to be transmitted when the second data was queued for transmission, the earlier data can be discarded to save from having to unnecessarily transmit/store it. Your watch wouldn't even have received the first data.

Since your watch would have only received one of the two pieces of data, this explains why you'd see "please retry" when you checked the received dictionary for one key, but it only contained the data for the other key.

How to transmit more than one item at once

Include both items in the same dictionary, and transmit that dictionary using a single transfer.

let data = ["number" : numberItem, "uidValue" : uidItem]
try WatchSessionManager.sharedManager.updateApplicationContext(data)
...

On the watch side, you simply can update the title label and uid label at the same time, instead of conditionally updating only one or the other.

if let numberItem = data["number"] as? String {
titleLabel.setText(numberItem)
}
if let uidItem = data["uidValue"] as? String {
uidLabel.setText(uidItem)
}

Why isn't iPhone transferring an array to Apple Watch?

session.reachable will only be true when the Watch app is in the foreground (there are a few rare exceptions).

If you want to send data to the watch app in the background, you should be using updateApplicationContext, transferUserInfo or transferFile (which one you choose depends on what you are sending and its size).

So something like:

Updated code in response to 3rd edit/question by originator

Phone code:

private func sendToWatch() {
do {
let applicationDict = ["Array1": array1, "Array2": array2]
try WCSession.defaultSession().updateApplicationContext(applicationDict)
}

catch {
print(error)
}
}

Watch code:

func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {

dispatch_async(dispatch_get_main_queue()) { () -> Void in

if let retrievedArray1 = applicationContext["Array1"] as? [String] {

array1 = retrievedArray1

print(array1)

}

if let retrievedArray2 = applicationContext["Array2"] as? [String] {

array2 = retrievedArray2

print(array2)

}

self.table!.setNumberOfRows(array1.count, withRowType: "tableRowController")

var index = 0

while index < array1.count {

let row = self.table.rowControllerAtIndex(index) as! tableRowController

row.rowLabel.setText(array1[index])

row.dateLabel.setText(array2[index])

index++

}

}
}

insertRowAtIndexes watchKit Swift

You have to do 3 steps:

  1. Add the new data to your array
  2. Insert a row into the table
  3. Populate the row with the new data

Here is a simple example:

class InterfaceController: WKInterfaceController {

@IBOutlet var table: WKInterfaceTable!
var items = ["row1", "row2", "row3"]

override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
loadTable()
}

func loadTable() {
table.setNumberOfRows(items.count, withRowType: "tableRow")
var rowIndex = 0
for item in items {
if let row = table.rowControllerAtIndex(rowIndex) as? TableRowController {
row.label.setText(item)
}
rowIndex++
}
}

@IBAction func insertRow() {
items.append("row4")
let newIndex = items.count
table.insertRowsAtIndexes(NSIndexSet(index: newIndex), withRowType: "tableRow")
if let row = table.rowControllerAtIndex(newIndex) as? TableRowController {
row.label.setText(items[newIndex])
}
}
}

TableRowController is a NSObject subclass that has one WKInterfaceLabel outlet to display the number of the row.

I used a button to trigger insertRow()

How to effectively load the properties string for all rows of data from realm into UILabels in a UITableView

Thanks to @Jay's help plus this website;

https://www.ralfebert.de/ios-examples/uikit/uitableviewcontroller/custom-cells/,
I found I was missing as! customCell from the end of the cellForRowAt func.

Here is how it should look if anyone is looking at this further down the line.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCellID", for: indexPath) as! customCell

let itemsToDisplay = realm.objects(RealmClassA.self).sorted(byKeyPath: "propertyA")[indexPath.row]

cell.propertyADisaplyLabel?.text = itemsToDisplay.propertyA
cell.propertyBDisplayLabel?.text = itemsToDisplay.propertyB
cell.propertyCDisplayLabel?.text = itemsToDisplay.propertyC


return cell
}

EDIT

Setting up a tableView dataSource on macOS - iOS is very similar. This sets up a dataSource with 10 objects. This example is really to show a proper use of the dataSource.

class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {

var dataArray = [String]()

@IBOutlet weak var myTableView: NSTableView!

override func viewDidLoad() {
super.viewDidLoad()

for i in 0...9 {
let s = "row_\(i)"
self.dataArray.append(s)
}

self.myTableView.delegate = self
self.myTableView.dataSource = self
self.myTableView.reloadData()
}

func numberOfRows(in tableView: NSTableView) -> Int {
return self.dataArray.count
}

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let identifier = NSUserInterfaceItemIdentifier("MyCellView")
guard let cell = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView else {return nil}

cell.textField?.stringValue = self.dataArray[row]
}

Note the NSTableCellView (Table Cell View) identifier is set to MyCellView.

WatchKit How set a string when a certain table is pressed?(Swift)

If I understand correctly you're trying to perform an action when a user taps on a WKInterfaceTable row?

You can achieve this using table:didSelectRowAtIndex: by identifying the corresponding string based on the index and sending an appropriate message to the string.

See the WKInterfaceTable Class Reference

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

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.

How to pass/parse server data to objects in WatchKit table rows?

Since your iOS host app has already downloaded and deserialized the data, it doesn't make any sense for the watch to duplicate that code or effort and GET the same data.

As for providing an example, you should show what you tried in code, and explain the specific problem you're having.

Documentation

You should use the Watch Connectivity framework to share data between your iOS and watchOS apps.

You'll find a good introduction in the watchOS 2 Transition Guide. See Communicating with Your Companion iOS App for details.

Apple also provides Lister sample code which demonstrates how to use WCSession to transfer both application context and files between iOS and watchOS.

Since the host app is written in Obj-C should WatchConnectivity / WCSessionDelegate be imported into every file header file that contains data that needs to be sent to the watch extension?

WCSession is a singleton that you configure at launch time, early in the life of both your iOS app and watch extension. See the transition guide's Activating the Session Object for more information.

If you don't understand how or where your apps should handle watch connectivity, there are plenty of tutorials and sample projects which you can easily find via Google.

So based on what you said I just need to use the Connectivity Framework. sendMessageToWatch and didReceiveMessage methods.

The exact methods you use depend on what you want to transfer -- application context, user info, files, or messages -- and whether it takes place in the foreground or background. See the transition guide's Choosing the Right Communication Option for more information.



Related Topics



Leave a reply



Submit