View not refreshing with state change

I'm currently prototyping a timer app in Swift Playgrounds.

The code for the top-level view and the child view is the following:

Code Block swift
import SwiftUI
import Foundation
import PlaygroundSupport
struct MultiTimerView: View {
    @State private var timerList = [TimerManager]()
    var body: some View {
        VStack(spacing: 20){
            ForEach(timerList.indices, id: \.self) { id in
                SingleTimerView(timerManager: self.$timerList[id])
            }
            Spacer()
            Button(action:addTimer){
                Image(systemName:"plus")
            }
        }
    }
    
    func addTimer(){
        timerList.append(TimerManager())
    }
}
struct SingleTimerView: View {
    @Binding var timerManager : TimerManager
    var body: some View {
        HStack(spacing: 20){
            Spacer()
            Stepper("\(timerManager.secondsLeft)", value: $timerManager.secondsLeft, in: 0...1000)
            Spacer()
            Button(action:self.timerManager.toggleTimer){
                Image( systemName: self.timerManager.timerRunning ? "pause.fill" : "play.fill")
            }
            Spacer()
        } 
    }
}
PlaygroundPage.current.setLiveView(MultiTimerView().padding(50))


The code for the TimerManager class is the following:

Code Block swift
import SwiftUI
import Foundation
import PlaygroundSupport
public class TimerManager: ObservableObject,Identifiable{
    @Published public var timerRunning = false
    @Published public var secondsLeft = 0
    var timer = Timer()
    public var id = UUID()
    public init() {}
    public func toggleTimer(){
        if timerRunning == false {
            timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true , block: { timer in
                if self.secondsLeft == 0 {
                    self.timerRunning = false
                    self.secondsLeft = 0
                    timer.invalidate()
                } else {
                    self.secondsLeft-=1
                }
            })
        } else {
            timer.invalidate()
        }
        timerRunning.toggle()
    }
}


The bug happens doing the following:
  • create a timer with the + button

  • load the timer a given amount of seconds with its stepper on the right side

  • press its play button

  • wait a couple of seconds and then add another timer

You'll see that the amount of remaining seconds for a running timer is not updated live, but only when refreshing the view by adding another timer to the list.

How can I fix this bug?

Answered by robnotyou in 641231022
You have made your TimerManager an ObservableObject, but your SingleTimerView does not handle it as an ObservedObject.

Fix:

In SingleTimerView, change
@Binding var timerManager : TimerManager
to
@ObservedObject var timerManager : TimerManager

In MultiTimerView, change
SingleTimerView(timerManager: self.$timerList[id])
to
SingleTimerView(timerManager: self.timerList[id])
Accepted Answer
You have made your TimerManager an ObservableObject, but your SingleTimerView does not handle it as an ObservedObject.

Fix:

In SingleTimerView, change
@Binding var timerManager : TimerManager
to
@ObservedObject var timerManager : TimerManager

In MultiTimerView, change
SingleTimerView(timerManager: self.$timerList[id])
to
SingleTimerView(timerManager: self.timerList[id])
View not refreshing with state change
 
 
Q