SwiftUI View updating

Solution 1:

You just have to observe the object at the appropriate level.

Each @Published only triggers a refresh if the object as a whole has changed.

In you example the array will change if you replace the array or add/remove objects.

import SwiftUI
struct ExerciseProgramView: View {
    //At this level you will see the entire program
    @StateObject var program: ExerciseProgram = ExerciseProgram()
    
    var body: some View {
        VStack{
            if program.currentExercise != nil{
                ExerciseView(exercise: program.currentExercise!)
            }else{
                Text("Ready?")
            }
            Spacer()
            HStack{
                if program.currentExercise == nil{
                    Button("start program", action: {
                        program.start()
                    })
                }else{
                    Button("stop", action: {
                        program.stop()
                    })
                    Button("next", action: {
                        program.next()
                    })
                }
                
            }
        }
    }
}
struct ExerciseView: View {
    //At this level you will see the changes for the exercise
    @ObservedObject var exercise: Exercise
    
    var body: some View {
        VStack{
            Text("\(exercise.name)")
            Text("\(exercise.currentSet)")
            if exercise.timer == nil{
                Button("start exercise", action: {
                    exercise.start()
                })
            }else{
                Button("stop exercise", action: {
                    exercise.stop()
                })
            }
        }.onDisappear(perform: {
            exercise.stop()
        })
    }
}

struct ExerciseProgramView_Previews: PreviewProvider {
    static var previews: some View {
        ExerciseProgramView()
    }
}
class Exercise: ObservableObject, Identifiable {
    let id: UUID = UUID()
    let name: String
    @Published var currentSet: Int = 1
    var timer : Timer?
    init(name: String){
        self.name = name
    }
    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in
            
            self.currentSet += 1
            
            if self.currentSet >= 10{
                timer.invalidate()
                self.timer = nil
            }
            
        })
    }
    
    func stop(){
        timer?.invalidate()
        timer = nil
    }
}

class ExerciseProgram: ObservableObject {
    @Published var currentExercise: Exercise? = nil
    @Published var exercises: [Exercise] = [Exercise(name: "neck"), Exercise(name: "arm"), Exercise(name: "leg"), Exercise(name: "abs")]
    @Published var exerciseIndex: Int = 0
    func start() {
        self.currentExercise = self.exercises[exerciseIndex]
    }
    
    func next(){
        if exerciseIndex < exercises.count{
            self.exerciseIndex += 1
        }else{
            self.exerciseIndex = 0
        }
        start()
    }
    
    func stop(){
        exerciseIndex = 0
        currentExercise = nil
    }
}

Also, notice how the ObservableObjects have been initialized.

@StateObject is used when the object has to be initialized in a View

@ObservedObject is used to pass an ObservableObject to a child View but the object was created inside a class, specifically class ExerciseProgram.

https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app