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 😀
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
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…
Show 7 more comments