Reputation: 73
I've created a trivial project to try to understand this better. Code below.
I have a source of data (DataSource
) which contains a @Published
array of MyObject
items. MyObject
contains a single string. Pushing a button on the UI causes one of the MyObject
instances to update immediately, plus sets off a timer to update a second one a few seconds later.
If MyObject
is a struct, everything works as I imagine it should. But if MyObject
is a class, then the refresh doesn't fire.
My expectation is that changing a struct's value causes an altered instance to be placed in the array, setting off the chain of updates. However, if MyObject
is a class then changing the string within a reference type leaves the same instance in the array. Array doesn't realise there has been a change so doesn't mention this to my DataSource
. No UI update happens.
So the question is – what needs to be done to cause the UI to update when the MyObject
class's property changes? I've attempted to make MyObject
an ObservableObject
and throw in some didchange.send()
instructions but all without success (I believe these are redundant now in any case).
Could anyone tell me if this is possible, and how the code below should be altered to enable this? And if anyone is tempted to ask why I don't just use a struct, the reason is because in my actual project I have already tried doing this. However I am using collections of data types which modify themselves in closures (parallel processing of each item in the collection) and other hoops to jump through. I tried re-writing them as structs but ran in to so many challenges.
import Foundation
import SwiftUI
struct ContentView: View
{
@ObservedObject var source = DataSource()
var body: some View
{
VStack
{
ForEach(0..<5)
{i in
HelloView(displayedString: self.source.results[i].label)
}
Button(action: {self.source.change()})
{
Text("Change me")
}
}
}
}
struct HelloView: View
{
var displayedString: String
var body: some View
{
Text("\(displayedString)")
}
}
class MyObject // Works if declared as a Struct
{
init(label: String)
{
self.label = label
}
var label: String
}
class DataSource: ObservableObject
{
@Published var results = [MyObject](repeating: MyObject(label: "test"), count: 5)
func change()
{
print("I've changed")
results[3].label = "sooner"
_ = Timer.scheduledTimer(withTimeInterval: 2, repeats: false, block: {_ in self.results[1].label = "Or later"})
}
}
struct ContentView_Previews: PreviewProvider
{
static var previews: some View
{
ContentView()
}
}
Upvotes: 3
Views: 2228
Reputation: 258355
When MyObject
is a class type the results
contains references, so when you change property of any instance inside results
the reference of that instance is not changed, so results
is not changed, so nothing published and UI is not updated.
In such case the solution is to force publish explicitly when you perform any change of internal model
class DataSource: ObservableObject
{
@Published var results = [MyObject](repeating: MyObject(label: "test"), count: 5)
func change()
{
print("I've changed")
results[3].label = "sooner"
self.objectWillChange.send() // << here !!
_ = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) {[weak self] _ in
self?.results[1].label = "Or later"
self?.objectWillChange.send() // << here !!
}
}
}
Upvotes: 1