Изменение ObservableObject в массиве @Published не обновляет представление
Я часами боролся с проблемой SwiftUI.
Вот упрощенный пример моей проблемы:
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
}
}
}
У меня есть ObservableObject (Parent), в котором хранится массив @Published ObservableObjects (Child).
Проблема в том, что когда свойство name изменяется с помощью задачи async для одного объекта в массиве, представление не обновляется.
Есть ли у вас какие-либо идеи?
Большое спасибо Николас
3 ответа
Убедитесь, что ваша детская модель struct
! Классы не обновляют пользовательский интерфейс должным образом.
Я бы сказал, это проблема дизайна. Ниже приведен предпочтительный подход, который использует только чистую функцию SwiftUI и не требует каких-либо обходных путей. Ключевой идеей является декомпозиция и явное внедрение зависимостей для "модели представления-представления".
Протестировано с 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())
}
}
Этот альтернативный подход работает для меня:
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()
}
}
}
}