View component doesn’t update when row is added to list

I have this view with a viewmodel too. I have an issue when I click on the add button the setRowView doesn’t update with a new row. I have tried to change med my @StateObject to an @ObsvervedObject, but that didn’t helped…

My SetViewModel is a class which also conform to the ObservableObject.
Exercise is also a class where the set property is and @Published.

class ExerciseCardViewModel: ObservableObject {
    
    @Published var execise: Execise
    
    init(execise: Execise) {
        self.execise = execise
    }
    
    func addSet() {
        execise.sets.append(DBSet(number: execise.sets.count + 1))
    }
    
    func deleteSet() {
        execise.sets.removeLast()
    }
    
    func createSetViewModel(sets: DBSet) -> SetViewModel {
        SetViewModel(sets: sets)
    }
}

struct ExerciseCard: View {
    @StateObject var viewModel: ExerciseCardViewModel
    
    var deleteExercise: (Execise) -> Void

    var body: some View {
        VStack {
            ForEach(viewModel.execise.sets) { set in
                SetsRowView(sets: viewModel.createSetViewModel(sets: set))
                    .listRowBackground(Color.theme.TBBlack)
            }
            
            HStack(spacing: 30) {
                button(action: {
                    viewModel.addSet()
                },
                       icon: "plus.circle",
                       title: "Set",
                       backgroundColor: .green)

                if !viewModel.execise.sets.isEmpty {
                    button(action: {
                        viewModel.deleteSet()
                    },
                           icon: "minus.circle",
                           title: "Set",
                           backgroundColor: .red)
                }
            }
            .padding(.horizontal)
        }
        .foregroundColor(Color.theme.TBLightGray)
        .padding()
        .background(RoundedRectangle(cornerRadius: 8)
            .foregroundColor(.theme.TBBlack))
    }

    private func button(
        action: @escaping () -> Void,
        icon: String,
        title: String,
        backgroundColor: Color) -> some View {
        Button {
            action()
        } label: {
            Text("\(Image(systemName: icon)) \(title)")
                .frame(width: 100, height: 30)
                .padding(.horizontal)
                .background(backgroundColor.opacity(0.3))
                .cornerRadius(8)
        }
    }
}

I hope you guys can help me 😀

  • On the Exercise card is that how you init your VM? If so I think it should be @StateObject var vm: ExerciseCardViewModel = ExerciseCardViewModel()

    – 

  • No I actually init it from another view 🤨

    – 

  • stackoverflow.com/questions/68710726/swiftui-view-updating/…

    – 

  • StateObject should always be private but my guess is that Excercise and are sets are reference types and not value types or something along the chain. Every ObservableObject needs a property wrapper to observe changes.

    – 




  • If I changed it too value types, the data is only alive within that view. which means I don’t have acces to it in the parent view…

    – 

Dear Frederik Hjorth,

you seem to be facing a problem with nested @Published variables. I experimented with something very similar:

class ExerciseProgram: ObservableObject  {
    @Published var currentExercise: Exercise
    @Published var currentSet: Int
}

The name of the currentExercise is not a problem since it only changes when a whole new Exercise class is assigned and ExerciseProgram is automatically aware when the object referenced by currentExercise changes.

In the case of currentExercise.currentSet (on the code above, I needed to make the ExerciseProgram itself contain a @Published property mirroring the data of my set.)

If you have @Published data nested in different layers of your code it becomes very messy. I personally like to use a ViewModel which is the only ObservedObject the View knows of, and make this class fetch/adapt and return data directly to the View, who’s only aware of the ViewModel and the values it provides, everything else being transparent.

Related thread

Leave a Comment