13

I did a exmaple long time ago how to send a simple message from an iPhone to a Apple Watch using Swift:

import UIKit
import WatchConnectivity

class ViewController: UIViewController, WCSessionDelegate {

    // MARK: Outlets

    @IBOutlet weak var textField: UITextField!

    // MARK: Variables

    var wcSession : WCSession! = nil

    // MARK: Overrides

    override func viewDidLoad() {
        super.viewDidLoad()

        wcSession = WCSession.default
        wcSession.delegate = self
        wcSession.activate()

    }

    // MARK: Button Actions

    @IBAction func sendText(_ sender: Any) {

        let txt = textField.text!
        let message = ["message":txt]

        wcSession.sendMessage(message, replyHandler: nil) { (error) in

            print(error.localizedDescription)

        }

    }

    // MARK: WCSession Methods
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

        // Code

    }

    func sessionDidBecomeInactive(_ session: WCSession) {

        // Code

    }

    func sessionDidDeactivate(_ session: WCSession) {

        // Code

    }

}

Now I'm trying to do the same using SwiftUI but no success so far. Can anyone help with this problem?

I just need to know how to use the WCSession Class and the WCSessionDelegate with SwiftUI.

Thanks

Max
  • 175
  • 1
  • 6

1 Answers1

21

I just had the same question as you and I figured it out:

First you need to implement a class that conforms to WCSessionDelegate. I like to use a separate class for that:

import WatchConnectivity

class ConnectivityProvider: NSObject, WCSessionDelegate {
    
    private let session: WCSession
    
    init(session: WCSession = .default) {
        self.session = session
        super.init()
        self.session.delegate = self
    }
    
    func send(message: [String:Any]) -> Void {
        session.sendMessage(message, replyHandler: nil) { (error) in
            print(error.localizedDescription)
        }
    }
    
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        // code
    }
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        // code
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        // code
    }
}

Now you need a ViewModel that takes your ConnectivityProvider as an argument. The ViewModel will be responsible for the connection of your View and the ConnectivityProvider. It also holds the value for the Textfield that later gets defined inside your View.

import SwiftUI  

final class ViewModel: ObservableObject {
    
    private(set) var connectivityProvider: ConnectivityProvider
    var textFieldValue: String = ""
    
    init(connectivityProvider: ConnectivityProvider) {
        self.connectivityProvider = connectivityProvider
    }
    
    func sendMessage() -> Void {
        let txt = textFieldValue
        let message = ["message":txt]
        connectivityProvider.send(message: message)
    }
}

Now you can build a simple View that consists of a Textfield and a Button. Your View will be dependent on your ViewModel that you just defined.

import SwiftUI

struct ContentView: View {
    
    @ObservedObject var viewModel: ViewModel
    
    var body: some View {
        VStack {
            TextField("Message Content", text: $viewModel.textFieldValue)
            
            Button(action: {
                self.viewModel.sendMessage()
            }) {
                Text("Send Message")
            }
        }
    }
}

Last but not least you need to combine your ConnectivityProvider, ViewModel and View inside of your SceneDelegate:

let viewModel = ViewModel(connectivityProvider: ConnectivityProvider())
let contentView = ContentView(viewModel: viewModel)

...

window.rootViewController = UIHostingController(rootView: contentView)

==================================

Update: How to activate the Session?

First add a new function to your ConnectivityProvider that activates the session:

class ConnectivityProvider: NSObject, WCSessionDelegate {
    ...
    func connect() {
        guard WCSession.isSupported() else {
            print("WCSession is not supported")
            return
        }
       
        session.activate()
    }
    ...
}

Now you can call the connect function whenever you need your WCSession to be connected. You should be able to connect it everywhere, like in your SceneDelegate, inside your ViewModel, or even directly inside of the init of your ConnectivityProvider:

ConnectivityProvider init:

class ConnectivityProvider: NSObject, WCSessionDelegate {

    private let session: WCSession

    init(session: WCSession = .default) {
        self.session = session
        super.init()
        self.session.delegate = self
        self.connect()
    }
    ...
}

ViewModel:

import SwiftUI  

final class ViewModel: ObservableObject {

    private(set) var connectivityProvider: ConnectivityProvider
    var textFieldValue: String = ""

    init(connectivityProvider: ConnectivityProvider) {
        self.connectivityProvider = connectivityProvider
        self.connectivityProvider.connect()
    }

    func sendMessage() -> Void {
        let txt = textFieldValue
        let message = ["message":txt]
        connectivityProvider.send(message: message)
    }
}

SceneDelegate:

let connectivityProvider = ConnectivityProvider()
connectivityProvider.connect()
let viewModel = ViewModel(connectivityProvider: connectivityProvider)
let contentView = ContentView(viewModel: viewModel)

...

window.rootViewController = UIHostingController(rootView: contentView)
KevinP
  • 2,562
  • 1
  • 14
  • 27
  • When do you activate the session? – Mdoyle1 Oct 01 '20 at 12:30
  • I did add a connect function to my ConnectivityProvider and I call it directly inside my SceneDelegate. But you can call it where you need it, for example inside of you ConnectivityProvider's init func. func connect() { guard WCSession.isSupported() else { print("WCSession is not supported") return } session.activate() } – KevinP Oct 01 '20 at 15:00
  • 2
    Do you have working code for this using swiftUI? I've been having a hard time getting the watch to receive messages from my Phone. I'm able to use the SimpleWatchConnectivity Project apple provides but that uses story boards. It would be nice to have a basic starting point for SwiftUI. I feel like there is something missing here, don't we have to setup the receiver function for the watch? In the above code there is no direction in what to do with the watch extension. – Mdoyle1 Oct 01 '20 at 16:05
  • are you using SwiftUI only ore SwiftUI with App and SceneDelegates? – KevinP Oct 01 '20 at 20:13
  • SwiftUI with App and SceneDelegate. Originally I thought it would be an easy task to pass some Defaults from my main iOS app to the Watch Extension. looks like I was wrong :) – Mdoyle1 Oct 02 '20 at 03:03
  • i did update my answer to include the activation process :) – KevinP Oct 02 '20 at 07:57
  • I'm getting this... Extension[347:40791] [WC] WCSession is missing its delegate In my Watch Extension, I have ComplicationControll.swift, ContentView.swift. how can I create the delegate from the Watch Extension... The SceneDelegate and AppDelegate are located with the iOS App. It seems like I'm good on the iOS side, just having problems on the Watchkit extension. – Mdoyle1 Oct 02 '20 at 14:59
  • You need to have one ConnectivityProvider in the app and one corresponding for the watch extension. You're right, there is no SceneDelegate inside the Extension, but you can use the HostingController in this case. Also consider reading the Documentation for WatchKit of Apple, its allot but it will help you allot if you want to build an successful WatchExtension. – KevinP Oct 02 '20 at 15:12
  • I'm getting this message. [WC] -[WCSession _onqueue_sendResponseError:identifier:dictionaryMessage:] identifier: with WCErrorCodeDeliveryFailed. There is communication with the watchOS and iOS now just not able to get the message through. Thanks for the help – Mdoyle1 Oct 02 '20 at 16:00
  • 1
    I finally got it! ConnectivityProvider is in my iOS app. I shared the target membership with the extension and initialized it in my Watch Extension's ContentView. Very tricky, but I had faith in your guidance thank your sir. – Mdoyle1 Oct 02 '20 at 17:26