iosswiftuidispatch-queue

SwiftUI Dispatch Queue does not update view properly


recently I’ve played around with SwiftUI (I’m not a Swift developer, this topic is quite new for me). I’ve found out strange behavior with a dispatch queue.

In my project, the business logic is match more complex; however, here is a simple example (see below).

The issue is the following: if you try to click the button frequently, the value of the actual boolean variable & the view could be different (see the attached screenshot below).

Could anybody give me a hint, what can I do with such an issue?

Note: the main app class is in comments to this post.

ContentView.swift

import SwiftUI

struct ContentView: View {
    @StateObject var model = ContentViewModel()
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(model.isCondition ? .green : .red)
            Button("TOGGLE") {
                model.foo()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

ContentViewModel.swift

import SwiftUI

final class ContentViewModel: ObservableObject {
    @Published private(set) var isCondition: Bool = true
    private var queue = DispatchQueue(label: "My Queue", qos: .background)
    
    func foo() {
        queue.async { [self] in
            isCondition.toggle()
            print("isCondition = \(isCondition.description)")
        }
    }
}

enter image description here


Solution

  • View (states) are required to be updated on the main thread when the body of the view is being computed. So try this approach, where you do your processing on your queue, but update the View state on the main thread.

    struct ContentView: View {
        @StateObject var model = ContentViewModel()
        var body: some View {
            VStack {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundColor(model.isCondition ? .green : .red)
                Button("TOGGLE") {
                    model.foo()
                }
                .buttonStyle(.borderedProminent)
            }
        }
    }
    final class ContentViewModel: ObservableObject {
        @Published private(set) var isCondition: Bool = true
        private var queue = DispatchQueue(label: "My Queue", qos: .background)
        
        func foo() {
            queue.async { [self] in
                // ... do calculations/processing here on your queue
                // but update isCondition on the main thread
                DispatchQueue.main.async {
                    self.isCondition.toggle()
                    print("isCondition = \(self.isCondition.description)")
                }
            }
        }
    }