Search code examples
swiftwatchkitios9watchos-2

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


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++

        }

    }

}

Solution

  • 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++
    
            }
    
        }
    }