I am building an app that will read a value over the Wi-Fi, provided by a Raspberry Pi and I am using MQTT server and client. Using UIKit was easy and the delegate methods were very user friendly in terms of the control over the matt server/client. However, I am building the app with SwiftUI, I have a view of an anemometer (wind sensor) display and I would like to get a NMEA and use the values from the string to display them on the view. I am struggling to maintain connection with the broker without using buttons in my view. I would like when the view is loaded to connect, subscribe to the topic, get the messages (which are being broadcasted constantly over the wi-fi) and display them. When I am using buttons for that it seems fine but if I use onAppear method it can't connect to the RPi and receive messages. Any good advices?. Here is my code:
//
// ContentView.swift
// ExtasyCompleteNavigation
//
// Created by Vasil Borisov on 13.06.23.
//
import SwiftUI
import CocoaMQTT
struct ContentView: View {
let mqttClient = CocoaMQTT(clientID: "Navigation", host: "raspberrypi.local", port: 1883)
var body: some View {
VStack {
AnemometerView()
Button("Connect"){
_ = mqttClient.connect()
}
Button("Subscribe"){
mqttClient.subscribe("windData")
}
Button("Get Message"){
mqttClient.didReceiveMessage = { mqtt, message, id in
print((message.string!))
}
}
Button("Disconnect"){
mqttClient.disconnect()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I tried to use .onAppear method, so it will connect once the view is loaded but it doesn't seem to work. If i check the server log it tells me that can't connect due to protocol error. Anybody experienced that?
EDIT-1 So, this is the code that I use in the .onAppear() method. I know that it would be better if I connect first, then in the didConnectAck method subscribe to the topic and then somehow receive the message in the didReceiveMessage closure. I just don't know how? Is there any work arounds which will mimic delegate methods as I used to use in UIKit? Or better way?
//
// ContentView.swift
// ExtasyCompleteNavigation
//
// Created by Vasil Borisov on 13.06.23.
//
import SwiftUI
import CocoaMQTT
struct ContentView: View {
let mqttClient = CocoaMQTT(clientID: "Navigation", host: "raspberrypi.local", port: 1883)
var windAngle = NavigationData()
var body: some View {
VStack {
AnemometerView()
.onAppear(){
_ = mqttClient.connect()
mqttClient.subscribe("windData")
mqttClient.didReceiveMessage = { mqtt, message, id in
if let safeMessage = message.string {
windAngle.windAngleString = safeMessage
print(windAngle.windAngleString)
print(windAngle.windAngleTest)
}
}
}
// Button("Connect"){
//
// }
// Button("Subscribe"){
//
// }
// Button("Get Message"){
//
// }
Button("Disconnect"){
mqttClient.disconnect()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Is there any work arounds which will mimic delegate methods as I used to use in UIKit? Or better way?
This is the approach (untested) I would use to connect to a Raspberry Pi using MQTT.
The important parts here are: separate the logic from the UI, hence use a MQTTManager
and
have this MQTTManager
be a CocoaMQTTDelegate
as well as
an ObservableObject
to listen to the messages received.
struct ContentView: View {
@StateObject var mqttManager = MQTTManager() // <--- here
var body: some View {
VStack {
Text(mqttManager.message) // <-- for testing
// AnemometerView()
Button("Disconnect"){
mqttManager.mqttClient.disconnect()
}
}
.onAppear {
mqttManager.mqttClient.connect()
// --- could be in didConnectAck, adjust as required
mqttManager.mqttClient.subscribe("windData")
}
}
}
// something like this
class MQTTManager: ObservableObject, CocoaMQTTDelegate { // <--- here
let mqttClient = CocoaMQTT(clientID: "Navigation", host: "raspberrypi.local", port: 1883)
@Published var message = ""
init() {
// ....
mqttClient.keepAlive = 60
mqttClient.delegate = self // <--- here
}
// .... CocoaMQTTDelegate functions ....
func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) {
print("topic: \(success)")
}
func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) {
print("topic: \(topics)")
}
func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) {
print("ack: \(ack)")
}
func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) {
print("message published: \(message.string.description), id: \(id)")
}
func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) {
print("id: \(id)")
}
func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) {
print("message received: \(message.string.description), id: \(id)")
if let str = message.string { // <--- here or something like this
self.message = str
}
}
func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) {
print("didSubscribeTopics")
}
func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopic topic: String) {
print("didUnsubscribeTopic")
}
func mqttDidPing(_ mqtt: CocoaMQTT) {
}
func mqttDidReceivePong(_ mqtt: CocoaMQTT) {
}
func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) {
print("\(err.description)")
}
}