NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

SOLVED: Help with @State/@ObservedObject data not updating in the UI

Forums > SwiftUI

I'm trying to write a dummy SMTP server for the desktop -- basically, a SwiftUI reimplementation of a JavaFX app I've been using for years. But first, I'm starting out really really simple. I've imported the BlueSocket library and started out with its example echo server. I have a UI that is just a checkbox (run/stop the server) and a text string that should display how many clients are currently connected. The server starts and stops just fine, but the UI isn't updating when a client connects.

I think the problem is that I don't have the UI observing the state properly. Could someone please help me out with this? Here's the relevant code, I think:

struct ContentView: View {
    // Using @ObservedObject and not @State because this view doesn't create or own the servers
    // but only reports out what the servers are up to.
    @ObservedObject var servers: Servers
// ...
    var body: some View {
        VStack {
            Text("Number of connections: \(servers.smtpServer.numberConnected)")
                .padding()
        }
    }
// ...
}

// Servers is a wrapper for all the servers the application will be running (SMTP, POP3)
// and manages the lifecycle for all of them.
class Servers: ObservableObject {
    @State var smtpServer = SMTPServer(port: 4000)
  // shutdown code deleted for brevity
}

class SMTPServer: ObservableObject {
// bunch of internal stuff deleted for brevity...
    var numberConnected: Int {
        return connectedSockets.count
    }
    private var connectedSockets = [Int32: Socket]()
// more stuff deleted...

    // We've received a connection so now we need to keep track of it. While it's alive, it will
    // sit in the connectedSockets dictionary.
    private func addNewConnection(socket: Socket) {
        // Add the new socket to the list of connected sockets...
        objectWillChange.send()
        socketLockQueue.sync { [unowned self, socket] in
            self.connectedSockets[socket.socketfd] = socket
        }

        // This is all just the echo server stuff
        // Get the global concurrent queue...
        let queue = DispatchQueue.global(qos: .default)

        // Create the run loop work item and dispatch to the default priority global queue...
        queue.async { [weak self, socket] in
            var shouldKeepRunning = true
            var readData = Data(capacity: SMTPServer.bufferSize)

            do {
                // Write the welcome string...
                try socket.write(from: "Hello, type 'QUIT' to end session\n")
                // ... the rest of the run loop is here, where we read from the client and echo back
            } while shouldKeepRunning

            // Here, we're done with the client and we're disconnecting. Note that we're still in a closure
            // that's running on the global DispatchQueue, so we can't actually update the UI. Does
            // that mean we need to fire off the change notification on the UI thread?

            self?.objectWillChange.send()
            print("Socket: \(socket.remoteHostname):\(socket.remotePort) closed...")
            socket.close()
            self?.socketLockQueue.sync { [weak self, socket] in
                _ = self?.connectedSockets.removeValue(forKey: socket.socketfd)
            }

   

im new to swiftUI but as far as i know in your server class you need to use @Published wrapper instead of @State. then observed object in your struct will see the obserevable object Server/ Smtp or whatever u have there

1      

Thanks for the tip about @Published. That's gotten me a bit farther along, although I'm still not seeing any updates to the number of clients in the UI until after the server stops. I'm going to refactor things a little and see if I can get it even simpler.

   

i think in smtp class its better to create published var and delete objectwillchange that way will be easier to understand why that class is observable and whats gonna be observed by others. again im new to swiftui hopefully more experienced members will join this thread and give good tips.

1      

Okay, I've tried out a few things and I believe I've gotten to the right implementation. In case anyone is wondering, here's the short version:

  • Servers does not need to be an ObservableObject since it is really just a container to let the AppDelegate shut everything down at quitting time
  • SMTPServer does need to be an ObservableObject, and only the properties marked with @Published will cause the observing view to be rebuilt when they change
  • Furthermore, computed properties can't be published, so I created two new ones, isRunning and numberOfConnections, and these get updated at the appropriate places in the code
  • Finally, the notifications have to be sent on the main thread. Since the socket stuff is all happening on background threads, the code to update the published properties needs to be wrapped with DispatchQueue.main.async {...}

ContentView winds up looking a little different:

struct ContentView: View {
    @ObservedObject var smtpServer: SMTPServer

    init(_ theServers: Servers) {
        smtpServer = theServers.smtpServer
    }

    var body: some View {
        VStack {
            HStack {
                Button(action: {
                    smtpServer.run()
                }) {
                    Text("Start SMTP")
                }
                    .disabled(smtpServer.isRunning)
                Spacer()
                Button(action: {
                    smtpServer.shutdown()
                }) {
                    Text("Stop SMTP")
                }
                .disabled(!smtpServer.isRunning)
            }
            Text("Number of connections: \(smtpServer.numberConnected)")
                .padding()
        }
    }
}

   

good for you! and it looks much nicer now. i would recomment u watching cs193 course for swiftUI on youtube. i dont like the way that guy does the actual code (its for advanced users i guess) but the way he explains complex things like generics, property wrappers and organizing the code is pretty good.

   

Hacking with Swift is sponsored by RevenueCat

SPONSORED Building and maintaining in-app subscription infrastructure is hard. Luckily there's a better way. With RevenueCat, you can implement subscriptions for your app in hours, not months, so you can get back to building your app.

Try it for free

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.