Sid
Sid

Reputation: 1063

SwiftUI DispatchQueue Button

I don't understand the issues around how SwiftUI and DispatchQueues work. Here is the code.

This works and will continue to refresh the counter state variable every second.

// Example 1 - This works
struct TimerButtonTest : View {
    @State var counter: Int = 0
    var body: some View {        
        start()
        return VStack { Text("\(counter)") }
    }

    func start() {
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
            self.counter += 1
        }
    }
}

This does not. After the button is pressed, the counter increments by 1 (after 1 second) but stops.

// Example 2 - This does not work
struct TimerButtonTest : View {
    @State var counter: Int = 0
    var body: some View {
        return Button(action: {self.start()}, label: {Text("\(counter)")})
    }

    func start() {
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000)) {
            self.counter += 1
        }
    }
}

Why doesn't the counter continue incrementing like the first example?

Upvotes: 4

Views: 5639

Answers (1)

Paulw11
Paulw11

Reputation: 114975

In the first piece of code you call start each time the body of the button is evaluated.

start (eventually) updates counter which is the state that is bound to the button's Text. Updating the bound state causes SwiftUI to evaluate the body var again. This calls start, which calls the asyncAfter and the process repeats indefinitely. As Rob pointed out in the comments, this isn't a very good approach - body might be called at any time and any number of times, so you can't guarantee that you will only get an update once per second.

In the second piece of code you only call start when the button is tapped. counter will be updated after a second and the Text updated. Nothing further will happen until you tap the button again.

Upvotes: 6

Related Questions