My basic requirement in to listen to a swift package’s variable’s value when connected to a sever and change a published value of same type inside view-model.
The code below is a sample code constructed to explain scenario. This code does not work properly as isConnected
value is not updated when server is connected.
In the sample code, a Swift package called ClientPackage
with its class and delegate protocol. Then there is ViewModel
class which SwiftUI uses to update state of UI and there is a LocalClient
class which implements ClientPackage
library. Basically LocalClient is used as an abstract local package to make implementation on same easier inside ViewModel. (I'm not sure if this is the right approach for this.) So I need to change isConnected
boolean in ViewModel
when server is connected inside ClientPackage
.
So I want to fixed this code in order to update isConencted
value updated. The connected
variable in LocalClient
is set correctly from delegate method.
How can I make the closure in ViewModel.connect()
work?
P.S. In the real app, I am using Starscream 4.0.4 swift package for ClientPackage
.
// SocketClient.swift
// Xcode 12.3
import Foundation
class ViewModel: ObservableObject {
var client: LocalClient?
@Published var isConnected: Bool?
let token = "12345678" // This is fetched async in real scenario
init() {
client = LocalClient(token: token)
}
func connect() {
client?.connect() { connected in
self.isConnected = connected
}
}
}
// Assuming code below is from a local swift package and can be modified
class LocalClient: NSObject {
var coreClient: ClientPackage?
var connected = false
init(token: String) {
super.init()
coreClient = ClientPackage(token: token)
coreClient!.delegate = self
}
func connect(_ completion: @escaping (Bool) -> Void) {
coreClient?.connectToServer()
completion(connected)
}
}
extension LocalClient: ClientPackageDelegate {
func connectedToServer(_ client: ClientPackage) {
connected = true
print("Connected to server")
}
func disconnectedFromServer(_ client: ClientPackage) {
connected = false
print("Disconnected from server")
}
}
// Assuming code below is from a swift package and cannot be modified
// import Starscream
open class ClientPackage {
open weak var delegate: ClientPackageDelegate?
open var socketConnected: Bool?
open var token: String?
public init(token: String) {
self.token = token
self.socketConnected = false
}
open func didConnect() {
self.delegate?.connectedToServer(self)
self.socketConnected = true
}
open func didDisconnect() {
self.delegate?.disconnectedFromServer(self)
self.socketConnected = false
}
open func connectToServer() {
// This method connect client to server asynchronously over websocket
// When connection stable, self.didConnect is called
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.didConnect()
}
}
open func disconnectFromServer() {
// This method disconnect the connection
// When connection stable, self.didDisconnect is called
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.didDisconnect()
}
}
}
public protocol ClientPackageDelegate: NSObjectProtocol {
func connectedToServer(_ client: ClientPackage)
func disconnectedFromServer(_ client: ClientPackage)
}
// ContentView.swift
import SwiftUI
struct ContentView: View {
@StateObject var vm = ViewModel()
var connected: Bool { vm.isConnected ?? false }
var body: some View {
VStack {
Button(connected ? "Disconnected" : "Connected"){
connected ? vm.disconnect() : vm.connect()
}
Text(connected ? "Connected" : "Disconnected")
}
}
}
You simply need to make the connected
property of LocalClient
@Published
, then update the isConnected
property of your view model whenever client.connected
is updated.
LocalClient
is already conforming to ClientPackageDelegate
and its connected
property is updated via the delegate methods, so you simply need to propagate the value of connected
to your view model, to update the isConnected
property which your UI observes.
class ClientViewModel: ObservableObject {
var client: LocalClient?
@Published var isConnected: Bool = false
let token = "12345678" // This is fetched async in real scenario
init() {
client = LocalClient(token: token)
client?.$connected.assign(to: &$isConnected)
}
func connect() {
client?.connect() { connected in
self.isConnected = connected
}
}
}
// Assuming code below is from a local swift package and can be modified
class LocalClient: NSObject {
var coreClient: ClientPackage?
@Published var connected = false
init(token: String) {
super.init()
coreClient = ClientPackage(token: token)
coreClient!.delegate = self
}
func connect(_ completion: @escaping (Bool) -> Void) {
coreClient?.connectToServer()
completion(connected)
}
}
If you want closure based behaviour rather than using Combine
(which I think is the wrong way to go, since you're already using Combine in your view model anyways), you just need to inject a closure into LocalClient
, which you execute from your delegate methods whenever the connectivity state changes.
class ClientViewModel: ObservableObject {
var client: LocalClient?
@Published var isConnected: Bool = false
let token = "12345678" // This is fetched async in real scenario
init() {
client = LocalClient(token: token, connectionStateChanged: { [weak self] in self?.isConnected = $0 })
}
func connect() {
client?.connect() { connected in
self.isConnected = connected
}
}
}
// Assuming code below is from a local swift package and can be modified
class LocalClient: NSObject {
let coreClient: ClientPackage
var connected = false
let connectionStateChanged: (Bool) -> Void
init(token: String, connectionStateChanged: @escaping (Bool) -> Void) {
self.connectionStateChanged = connectionStateChanged
coreClient = ClientPackage(token: token)
super.init()
coreClient.delegate = self
}
func connect(_ completion: @escaping (Bool) -> Void) {
coreClient.connectToServer()
completion(connected)
}
}
extension LocalClient: ClientPackageDelegate {
func connectedToServer(_ client: ClientPackage) {
self.connected = true
connectionStateChanged(true)
print("Connected to server")
}
func disconnectedFromServer(_ client: ClientPackage) {
self.connected = false
connectionStateChanged(false)
print("Disconnected from server")
}
}