MQTT Using SwiftUI

Byuvaraj
5 min readDec 3, 2023

--

Introduction

In the fast-paced world of mobile applications, real-time communication has become a crucial aspect of user engagement. MQTT (Message Queuing Telemetry Transport) is a lightweight and efficient protocol that facilitates real-time communication between devices. In this blog post, we’ll explore how to integrate MQTT into an iOS app built with SwiftUI, Apple’s modern and declarative UI framework.

Why MQTT?

MQTT is known for its simplicity, low overhead, and reliability, making it an excellent choice for scenarios where real-time updates are essential. Whether you’re building a messaging app, live tracking system, or IoT application, MQTT provides a scalable solution that minimizes latency and conserves battery life.

Setting Up the Project

Start by creating a new SwiftUI project in Xcode. Open Xcode, select “Create a new Xcode project,” choose the SwiftUI template, and give your project a name.

We are going to use CocoaMQTT Library for MQTT. As mentioned in the library link complete the libaray integration in xcode.

Implementing MQTT in SwiftUI

Step 1: Import the CocoaMQTT library

import CocoaMQTT

Step 2: Initialize and connect MQTT Broker domain

To Initialize first setup the broker domain adress using swiftUI code.

In mqtt manager class we have initializing the connection and disconnection

 private func configureAndConnect() {
// Initialize the MQTT Manager
mqttManager.initializeMQTT(host: brokerAddress, identifier: UUID().uuidString,username: "your username",password: "your password")
// Connect
mqttManager.connect()
}

private func disconnect() {
mqttManager.disconnect()
}
 
private var mqttClient: CocoaMQTT?
@Published var currentAppState = MQTTAppState()

func initializeMQTT(host: String, identifier: String, username: String? = nil, password: String? = nil) {
if mqttClient != nil {
mqttClient = nil
}
self.identifier = identifier
self.host = host
self.username = username
self.password = password
let clientID = "CocoaMQTT-\(identifier)-" + String(ProcessInfo().processIdentifier)
mqttClient = CocoaMQTT(clientID: clientID, host: host, port: 1234)
if let finalusername = self.username, let finalpassword = self.password {
mqttClient?.username = finalusername
mqttClient?.password = finalpassword
}
mqttClient?.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout")
mqttClient?.keepAlive = 60
mqttClient?.delegate = self
}

func connect() {
mqttClient?.connect()
}

func subscribe(topic: String) {
mqttClient?.subscribe(topic, qos: .qos1)
}

func publish(with message: String) {
mqttClient?.publish(topic, withString: message, qos: .qos1)
}

Broker address will be get from textfied and user name and password as per CocoaMQTT Auth

Creating a Delegates

Now that we have set up the connection, we need to subscribe to a topic and handle any incoming messages. To do this, we are using a CocoaMQTTDelegate. This extension will contain quite a few methods, which are required in order to conform to the CocoaMQTTDelegate protocol.

extension MQTTManager: CocoaMQTTDelegate {
func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) {
TRACE("topic: \(success)")
}

func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) {
TRACE("topic: \(topics)")
}

func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) {
TRACE("ack: \(ack)")
}

func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) {
TRACE("message: \(message.string.description), id: \(id)")
}

func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) {
TRACE("id: \(id)")
}

func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) {
TRACE("message: \(message.string.description), id: \(id)")
}

func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopic topic: String) {
TRACE("topic: \(topic)")
}

func mqttDidPing(_ mqtt: CocoaMQTT) {
TRACE()
}

func mqttDidReceivePong(_ mqtt: CocoaMQTT) {
TRACE()
}

func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) {
TRACE("\(err.description)")
}
}

Once Connected to domain you will receive the didConnectAck and further didConnect and didReceiveMessage delegates will call.

Subscribe a Topic

Subscribe to the topic after the connection is established and you won’t run into issues by trying to subscribe before a connection exists. Thus, add the following to the didConnect method:

func subscribe(topic: String) {
mqttClient?.subscribe(topic, qos: .qos1)
}

Receiving a Messages

Now that we have subscribed to the appropriate topic, we can now handle receiving messages from the MQTT broker. We will get messages in didReceiveMessage method using the code below:

 func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) {
TRACE("message: \(message.string.description), id: \(id)")
currentAppState.setReceivedMessage(text: message.string.description)
}

Check this sample code in SwiftUI to send message and receive in textview

struct MessageView: View {
@State var topic: String = ""
@State var message: String = ""
@EnvironmentObject private var mqttManager: MQTTManager
var body: some View {
VStack {
Spacer()
VStack {
HStack {
MQTTTextField(placeHolderMessage: "Enter topic", isDisabled: !mqttManager.isConnected() || mqttManager.isSubscribed(), message: $topic)
Button(action: functionFor(state: mqttManager.currentAppState.appConnectionState)) {
Text(titleForSubscribButtonFrom(state: mqttManager.currentAppState.appConnectionState))
.font(.system(size: 14.0))
}.buttonStyle(BaseButtonStyle(foreground: .white, background: .blue))
.frame(width: 100)
.disabled(!mqttManager.isConnected() || topic.isEmpty)
}

HStack {
MQTTTextField(placeHolderMessage: "Enter a message", isDisabled: !mqttManager.isSubscribed(), message: $message)
Button(action: { send(message: message) }) {
Text("Send").font(.body)
}.buttonStyle(BaseButtonStyle(foreground: .white, background: .blue))
.frame(width: 80)
.disabled(!mqttManager.isSubscribed() || message.isEmpty)
}
MessageHistoryTextView(text: $mqttManager.currentAppState.historyText
).frame(height: 150)
}.padding(EdgeInsets(top: 0, leading: 7, bottom: 0, trailing: 7))

Spacer()
}
.background(Color.black)
.navigationTitle("Messages")
}
final class MQTTAppState: ObservableObject {
@Published var appConnectionState: MQTTAppConnectionState = .disconnected
@Published var historyText: String = ""
private var receivedMessage: String = ""

func setReceivedMessage(text: String) {
receivedMessage = text
historyText = historyText + "\n" + receivedMessage
}

func clearData() {
receivedMessage = ""
historyText = ""
}

func setAppConnectionState(state: MQTTAppConnectionState) {
appConnectionState = state
}
}
import Combine
import Foundation

enum MQTTAppConnectionState {
case connected
case disconnected
case connecting
case connectedSubscribed
case connectedUnSubscribed

var description: String {
switch self {
case .connected:
return "Connected"
case .disconnected:
return "Disconnected"
case .connecting:
return "Connecting"
case .connectedSubscribed:
return "Subscribed"
case .connectedUnSubscribed:
return "Connected Unsubscribed"
}
}
var isConnected: Bool {
switch self {
case .connected, .connectedSubscribed, .connectedUnSubscribed:
return true
case .disconnected,.connecting:
return false
}
}

var isSubscribed: Bool {
switch self {
case .connectedSubscribed:
return true
case .disconnected,.connecting, .connected,.connectedUnSubscribed:
return false
}
}
}

Thats’ it.

Now the application is set up to display any messages from the MQTT broker in the textview. So, CocoaMQTT is the solution to the problem.

Thanks for reading…,

--

--