Reputation: 3221
I have an array stored on my iPhone that I need sent to the Watch in the background whenever it is updated on the phone. I've followed a tutorial on RayWenderlich.com but I just can't seem to get it to work.
Phone code:
import UIKit
import Foundation
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
var array1 = ["Hi", "This", "Is", "A", "Test"]()
var array2 = ["1", "2", "3", "4", "5"]()
var session: WCSession?
private func startSession() {
if WCSession.isSupported() {
session = WCSession.defaultSession()
session?.delegate = self
session?.activateSession()
}
}
private func sendToWatch() {
if let session = session where session.reachable {
session.transferUserInfo(["Array1": array1])
}
if let session = session where session.reachable {
session.transferUserInfo(["Array2": array2])
}
}
sendToWatch()
}
And Watch code:
import WatchKit
import Foundation
import WatchConnectivity
var array1 = [String]()
var array2 = [String]()
class InterfaceController: WKInterfaceController, WCSessionDelegate {
var session: WCSession?
@IBOutlet var table: WKInterfaceTable!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
startSession()
table!.setNumberOfRows(array1.count, withRowType: "tableRowController")
var index = 0
while index < array1.count {
let row = table.rowControllerAtIndex(index) as! tableRowController
row.rowLabel.setText(array1[index])
row.dateLabel.setText(array2[index])
index++
}
}
private func startSession() {
if WCSession.isSupported() {
session = WCSession.defaultSession()
session?.delegate = self
session?.activateSession()
}
}
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let received1 = userInfo["Array1"] as? [String] {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
array1 = received1
})
}
if let received2 = userInfo["Array2"] as? [String] {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
array2 = received2
})
}
}
}
The Table on the watch works fine if I enter dummy data into the arrays, so I know everything is set up correctly. I'm really only having problems with the Transfer/Receiving.
SECOND ATTEMPT:
I'm now trying to use updateApplicationContext()
as opposed to transferUserInfo()
. I've removed the session.reachable
check and changed some code.
Phone code:
private func sendToWatch() {
do {
let applicationDict = ["Array1": array1]
let applicationDict2 = ["Array2": array2]
try WCSession.defaultSession().updateApplicationContext(applicationDict)
try WCSession.defaultSession().updateApplicationContext(applicationDict2)
}
catch {
print(error)
}
}
Watch code:
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
//I can't figure out how to retrieve the data!
print(applicationContext) // returns nil
array1 = applicationContext["Array1"] as! [String] //returns nil and crashes app
array2 = applicationContext["Array2"] as! [String] //returns nil and crashes app
}
THIRD ATTEMPT:
I'm now able to sometimes get either array1 or array2 to appear on the Watch. Of course, it crashes the app as both arrays are not successfully received. Can anybody help me with this?
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]) //My crash happens either here or....
row.dateLabel.setText(array2[index]) //here because both arrays aren't being received and unreceived array is out of index
index++
}
}
}
Upvotes: 1
Views: 1742
Reputation: 4656
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++
}
}
}
Upvotes: 3
Reputation: 414
With respect to your third try:
You don't want to use updateApplicationContext - you want to use transferUserInfo, as elsewhere suggested.
Each updateApplicationContext call will overwrite any preceding data that have have been sent to the watch. When you send dict2 immediately after the dict1, the first dictionary is discarded and replaced with dict2. You cannot get both dictionaries in this way via updateApplicationContext.
You would use updateApplicationContext to pass, for example, settings or options in your app where only the most recent setting is important. For example, if you have a flag that could be changed in the iOS app, you would use updateApplicationContext every time the flag was changed in the iOS app. When the watch app eventually is loaded, it will only see the last setting of the flag in didReceiveApplicationContext.
The way your third try is set up now, the first dict is sent and then immediately followed by the second dict. You are seeing intermittent failures based on variable lag times in the delivery of those two dictionaries.
If the didReceiveApplicationContext is invoked before the second dictionary is completely transferred, you will successfully retrieve dictionary 1 since that data hasn't been overwritten yet. It will fail when it tries to access index "Array 2" because it is not there yet. If the second context is transferred before the watch hits the retrieve "Array 1" call, then your app will fail there because the last data received is not keyed on "Array1", but rather "Array2".
Calls using updateUserInfo do not overwrite preceding calls. When the watch app is loaded, it will trigger the didReceiveUserInfo code once for each call. The order of the calls is maintained, so if you sent dict1, dict2, and then dict3 from the iOS app, the watch app will get them in that order.
Upvotes: 4