为什么嵌入到EnvironmentObject中的ObservableObject不向列表中添加新项目

时间:2019-12-10 20:17:42

标签: swift object observable swiftui-list

我正在学习SwiftUI并修改了一个现有示例,因为我想测试是否可以在视图之间访问ObservableObject。阅读文档时,我发现我应该为此使用@EnvironmentObject。我试过了,但是这不起作用:数组人被新人填充(使用调试器),但是UI没有更新。如果我在@EnvironmentObject之外使用people属性,它将按预期工作。

我的代码:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var envTest: EnvTest
    var body: some View {
        VStack {
            ForEach(envTest.people.persons) { person in
                Text("\(person.name)")
            }
            Button(action: {
                self.envTest.people.persons.append(Person(id: 4, name: "I am new here"))
            }) {
                Text("Add/Change name")
            }
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static let envTest = EnvTest()

    static var previews: some View {
        ContentView().environmentObject(envTest)
    }
}

class Person: ObservableObject, Identifiable {
    var id: Int
    @Published var name: String

    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
}

class People: ObservableObject {
    @Published var persons: [Person]

    init() {
        self.persons = [
            Person(id: 1, name: "Jabba"),
            Person(id: 2, name: "Polke"),
            Person(id: 3, name: "Lori")]
    }
}

class EnvTest: ObservableObject {
    @ObservedObject var people = People()
}

我使用以下命令将EnvTest添加到SceneDelegate:

var window: UIWindow?
var envTest = EnvTest()

func scene(...) {

    let contentView = ContentView().environment(\.managedObjectContext, context).environmentObject(envTest)
}

2 个答案:

答案 0 :(得分:1)

要获得所需的结果,有两个问题需要解决:

  1. 导入Combine并将@ObservedObject中的EnvTest替换为@Published
  2. 当“ people”属性发生变化时,通过将订阅者添加到EnvTest.objectWillChange属性的objectWillChange中,手动触发people

这两个更改的原因与ObservableObject的工作方式有关。 documentation告诉我们

  

一个ObservableObject合成一个objectWillChange发布者,该发布者在其@Published属性中的任何一个发生更改之前就发出更改后的值。

如果您没有@Published属性,那么People.objectWillChange发布者将不会发出任何信息。解决此问题后,当我们更改persons上的People属性时,就会触发People.objectWillChange发布者发出一个值。但是,揭示了第二个问题,SwiftUI正在监听EnvTest.objectWillChange发布者,因此我们不会看到UI更新。这就是为什么当我们从send发布者那里收到一个值时,我们必须在EnvTest.objectWillChange发布者上手动调用People.objectWillChange来通知SwiftUI它需要生成新的视图。

如果将objectWillChange属性包装器放在符合@Published的类型的属性上时,如果Combine自动传播的ObservableObject调用会很好。同样,如果SwiftUI观看最低的ObservableObject而不是观看最高的People(而不是观看EnvTest),那就太好了。也许将来会引入这种方法,因为当前手动链接两者的解决方案很痛苦,而且伸缩性不好。

执行所需代码的完整解决方案是:

import SwiftUI
import Combine

struct ContentView: View {
    @EnvironmentObject var envTest: EnvTest
    var body: some View {
        VStack {
            ForEach(envTest.people.persons) { person in
                Text("\(person.name)")
            }
            Button(action: {
                self.envTest.people.persons.append(Person(id: 4, name: "I am new here"))
            }) {
                Text("Add/Change name")
            }
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static let envTest = EnvTest()

    static var previews: some View {
        ContentView().environmentObject(envTest)
    }
}

class Person: ObservableObject, Identifiable {
    var id: Int
    @Published var name: String

    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
}

class People: ObservableObject {
    @Published var persons: [Person]

    init() {
        self.persons = [
            Person(id: 1, name: "Jabba"),
            Person(id: 2, name: "Polke"),
            Person(id: 3, name: "Lori")]
    }
}

class EnvTest: ObservableObject {
    @Published var people = People()

    init(people: People = People()) {
        self.people = people
        people.objectWillChange.receive(subscriber: Subscribers.Sink(receiveCompletion: { _ in }) {
            self.objectWillChange.send()
        })
    }
}

答案 1 :(得分:0)

好吧,这似乎是不可能的,但是随着我对Swift的了解越来越好,我知道我可以轻松地在视图之间共享可观察对象,而无需将其嵌入到EnvironmentObject中。