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:
- Add the new data to your array
- Insert a row into the table
- 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
anddidReceiveMessage
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
Uisearchcontroller Hiding Status Bar on iOS 11 Swift 4
How to Remove The Fading Animation on .Ondelete Swiftui
Xcode Swift: Could Not Insert New Outlet Connection
How to Show Cluster for Mkpolyline with Geojson Data View in iOS Swift
Customize Mglpolyline Using Mapbox
Touch Sprite, Make It Jump Up Then Fall Down Again(Repeat as Many Times as Spritenode Is Tapped.)
Mkpolyline Broken When Using Type Satelliteflyover
How to Constrain Second Nsviewcontroller Minimum Size in Os X App
Can't Update Label from Other Class in Swift
How to Download Datas from Multiple Url in Concurrency Mode
Swift: How to Continuously Send an Action from a Nstextfield
Swift: How to Update UI When Listening to a Stream
Prevent Nscollectionview 'Lifting' an Item During Drag
Get Out of Navigation Controller and Go Back to Tab Bar View
How to Get The Advantages of Scenekit's Level Editor Programatically