Reputation: 59
I've been struggling for hours on an issue with SwiftUI.
Here is a simplified example of my issue :
class Parent: ObservableObject {
@Published var children = [Child()]
}
class Child: ObservableObject {
@Published var name: String?
func loadName() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Async task here...
self.objectWillChange.send()
self.name = "Loaded name"
}
}
}
struct ContentView: View {
@ObservedObject var parent = Parent()
var body: some View {
Text(parent.children.first?.name ?? "null")
.onTapGesture {
self.parent.objectWillChange.send()
self.parent.children.first?.loadName() // does not update
}
}
}
I have an ObservableObject (Parent) storing a @Published array of ObservableObjects (Child).
The issue is that when the name property is changed via an async task on one object in the array, the view is not updated.
Do you have any idea ?
Many thanks Nicolas
Upvotes: 5
Views: 6532
Reputation: 8687
Make sure your Child model is a struct
! Classes doesn't update the UI properly.
Upvotes: 2
Reputation: 257493
I would say it is design issue. Please find below preferable approach that uses just pure SwiftUI feature and does not require any workarounds. The key idea is decomposition and explicit dependency injection for "view-view model".
Tested with Xcode 11.4 / iOS 13.4
class Parent: ObservableObject {
@Published var children = [Child()]
}
class Child: ObservableObject {
@Published var name: String?
func loadName() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Async task here...
self.name = "Loaded name"
}
}
}
struct FirstChildView: View {
@ObservedObject var child: Child
var body: some View {
Text(child.name ?? "null")
.onTapGesture {
self.child.loadName()
}
}
}
struct ParentContentView: View {
@ObservedObject var parent = Parent()
var body: some View {
// just for demo, in real might be conditional or other UI design
// when no child is yet available
FirstChildView(child: parent.children.first ?? Child())
}
}
Upvotes: 2
Reputation: 36146
this alternative approach works for me:
class Parent: ObservableObject {
@Published var children = [Child()]
}
class Child: ObservableObject {
@Published var name: String?
func loadName(handler: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Async task here...
self.name = UUID().uuidString // just for testing
handler()
}
}
}
struct ContentView8: View {
@ObservedObject var parent = Parent()
var body: some View {
Text(parent.children.first?.name ?? "null").padding(10).border(Color.black)
.onTapGesture {
self.parent.children.first?.loadName(){
self.parent.objectWillChange.send()
}
}
}
}
Upvotes: -1