Home > OS >  SwiftUI view does not update for Published objects with subclasses
SwiftUI view does not update for Published objects with subclasses

Time:11-28

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] = []
}

Screenshot here

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 {
    
}
  • Related