Search code examples
arrayswatchkitswift4watchconnectivity

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


I am attempting to pass an array between iPhone and Apple Watch Apps in Swift 4, thus far I have been unsuccessful. Here are a few examples I have tried to follow, unsuccessfully. In those examples they don't include many of the functions, however when I try and write it like that it gives the error does not conform to protocols in addition when I declare the watch session It doesn't declare properly, also the didRecieveApplicationContext is very different in swift 4 than as shown in the other questions/ examples Link Link2

Here is my iPhone ViewController

import UIKit
import WatchConnectivity

class watchtimetablemaker: UIViewController, WCSessionDelegate {

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}

func sessionDidBecomeInactive(_ session: WCSession) {
}

func sessionDidDeactivate(_ session: WCSession) {
}


var watchSession: WCSession?
var initalArray = [String]()
var combination = ""
var vdlArray = [String]()


@IBAction func Save(_ sender: Any) {

    vdlArray.removeAll()

//here I edit the arrays, add instances etc. cut out for fluency

    self.view.endEditing(true)

    sendToWatch()

    UserDefaults.standard.set(vdlArray, forKey: "vdlarray")
    UserDefaults.standard.synchronize()

    print(initalArray)
    print(vdlArray)

}

func sendToWatch(){
    do{
        let appDictionary = ["Array": initalArray]
        try WCSession.default.updateApplicationContext(appDictionary)
    }
    catch {
        print(error)
    }
}

override func viewDidLoad() {
    super.viewDidLoad()

    if (WCSession.isSupported()){
        watchSession = WCSession.default
        watchSession?.delegate = self
        watchSession?.activate()
    }

    vdlArray = UserDefaults.standard.array(forKey: "vdlarray") as! [String]

    print(vdlArray)

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}
}

Here is the Apple Watch Controller

import WatchKit
import Foundation
import WatchConnectivity


class TimetableWatch: WKInterfaceController, WCSessionDelegate {

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
    load()
}

@IBOutlet var tabletitle: WKInterfaceTable!
var watchSession: WCSession?
var tableData = [String]()

override func awake(withContext context: Any?) {
    super.awake(withContext: context)

    if (WCSession.isSupported()){
        watchSession = WCSession.default
        watchSession?.delegate = self
        watchSession?.activate()
    }

    load()

    tabletitle.setNumberOfRows(tableData.count, withRowType: "Cell")

    var i = 0
    for item in tableData {
        let row = tabletitle.rowController(at: i) as! TableCellRow

        row.tbalecelllabel.setText(item)

        i = i + 1
    }
}

func load(){

    func session(session: WCSession, didRecieveApplicationContext applicationContext: [String : Any]) {

        DispatchQueue.main.async() { () -> Void in
            if let retreivedArray = applicationContext["Array"] as? [String] {
                self.tableData = retreivedArray
                print(self.tableData)
            }
        }
    }
}

override func willActivate() {
    super.willActivate()
}

override func didDeactivate() {
    super.didDeactivate()
}
}

I have been unable to find any documentation relating to swift4 so, any help would be much appreciated.


Solution

  • 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.