Because my actual code is a bit more complicated, here is a simplified class structure with which I can reproduce the same unexpected behavior.
This is my base data object which I subclass:
class People: Identifiable {
var name: String
required init(name: String) {
self.name = name
}
}
class Men: People {
}
And then I use another class which acts also as superclass, but also uses a generic type of People
.
class SuperMankind<PlayerType: People> {
var people: [PlayerType] = []
}
class Mankind: SuperMankind<Men> {
}
Now I want to use this this Mankind
subclass in my ViewModel, which is an ObservableObject.
class ViewModel: ObservableObject {
@Published var mankind: Mankind
init(_ m: Mankind) {
mankind = m
}
}
struct TestView: View {
@StateObject var viewModel = ViewModel(Mankind())
var body: some View {
VStack {
Button("Add") {
viewModel.mankind.people.append(Men(name: Int.random(in: 0...1000).description))
}
List {
ForEach(viewModel.mankind.people) {
Text($0.name)
}
}
}
}
}
But my view does not update if I click the add button and I don't know why. I figured out that if I add the following code to my button action the view updates. But this manual call should not be necessary in my opinion so I assume I do something wrong.
viewModel.objectWillChange.send()
CodePudding user response:
ObservableObject
requires that its fields are structs, not classes.
I changed your code slightly and it worked:
protocol SuperMankind {
associatedtype PlayerType
var people: [PlayerType] { get set }
}
struct Mankind: SuperMankind {
var people: [Men] = []
}
Re your solution (since I can't comment):
Array<Men>
is a struct, despite the array holding class references. This is why your code works now, as before you were directly holding a reference to a class in your ObservableObject
(which therefore did not update the view).
CodePudding user response:
@SwiftSharp thanks for your answer and the associatedType
I didn't thought about this. But that @Published
fields need to be structs is incorrect think about this solution, which I will choose for now, because I don't want to make all my functions mutating.
class People: Identifiable {
var name: String
required init(name: String) {
self.name = name
}
}
class Men: People {
}
class SuperMankind<PlayerType: People>: ObservableObject {
@Published var people: [PlayerType] = []
}
class Mankind: SuperMankind<Men> {
}
struct TestView: View {
@StateObject var viewModel = Mankind()
var body: some View {
VStack {
Button("Add") {
viewModel.people.append(Men(name: Int.random(in: 0...1000).description))
}
List {
ForEach(viewModel.people) {
Text($0.name)
}
}
}
}
}
My problem was that the ViewModel
class was not necessary and that my superclass, which holds the people array was not an ObervableObject.
Edit just to be complete here:
This would also fix my initial code problem, with the usage of the ViewModel class, but instead of subclassing ObservableObject I would subclass from Mankind
, which already conforms to ObservableObject by subclassing SuperMankind:
class ViewModel: Mankind {
}