Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send Message to Apple Watch using WCSession in SwiftUI

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

like image 422
Max Avatar asked Jan 18 '20 19:01

Max


1 Answers

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)
like image 91
KevinP Avatar answered Oct 05 '22 02:10

KevinP