如何告诉SwiftUI视图绑定到嵌套的ObservableObjects

rjk*_*lan 6 ios swift swiftui combine

我有一个SwiftUI视图,其中包含一个名为的EnvironmentObject appModel。然后,它读取appModel.submodel.countbody方法中的值。我希望这能将我的视图绑定到该属性countsubmodel以便在属性更新时重新呈现该属性,但这似乎没有发生。

这是错误吗?如果不是,将视图绑定到SwiftUI中环境对象的嵌套属性的惯用方式是什么?

具体来说,我的模型看起来像这样...

class Submodel: ObservableObject {
  @Published var count = 0
}

class AppModel: ObservableObject {
  @Published var submodel: Submodel = Submodel()
}
Run Code Online (Sandbox Code Playgroud)

我的观点看起来像这样...

struct ContentView: View {
  @EnvironmentObject var appModel: AppModel

  var body: some View {
    Text("Count: \(appModel.submodel.count)")
      .onTapGesture {
        self.appModel.submodel.count += 1
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

当我运行该应用程序并单击标签时,count属性会增加,但标签不会更新。

我可以通过将appModel.submodel属性作为传入来解决此问题ContentView,但如果可能的话,我想避免这样做。

Hie*_*ham 28

Sorin Lica 的解决方案可以解决这个问题,但是这会在处理复杂视图时导致代码异味。

似乎更好的建议是仔细审视你的观点,并修改它们以提出更多、更有针对性的观点。构造视图,以便每个视图显示对象结构的单个级别,将视图与符合ObservableObject. 在上面的情况下,您可以创建一个用于显示的视图Submodel(甚至多个视图),该视图显示您想要显示的属性。将属性元素传递给该视图,并让它为您跟踪发布者链。

struct ContentView: View {
  @EnvironmentObject var appModel: AppModel

  var body: some View {
    SubView(submodel: appModel.submodel)
  }
}

struct SubView: View {
  @ObservedObject var submodel: Submodel

  var body: some View {
      Text("Count: \(submodel.count)")
      .onTapGesture {
        self.submodel.count += 1
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

这种模式意味着制作更多、更小、更集中的视图,并让 SwiftUI 内部的引擎进行相关跟踪。这样您就不必处理簿记问题,而且您的观点也可能会变得更加简单。

您可以在这篇文章中查看更多详细信息:https://rhonabwy.com/2021/02/13/nested-observable-objects-in-swiftui/

  • 本页的答案是黄金的。谢谢。它不仅解释了问题,而且比整个传递 objectWillChange 上游地狱更优雅,如上所述,这会导致许多不必要的 UI 更新。https://rhonabwy.com/2021/02/13/nested-observable-objects-in-swiftui/ (3认同)
  • 这个解决方案很棒,但是它似乎不适用于iOS13 (2认同)

hec*_*ckj 10

我最近在我的博客上写了相关内容:嵌套可观察对象。如果您确实想要 ObservableObjects 的层次结构,解决方案的要点是创建您自己的顶级组合主题以符合ObservableObject 协议,然后将您想要触发更新的任何逻辑封装到更新该内容的命令式代码中主题。

例如,如果您有两个“嵌套”类,例如

class MainThing : ObservableObject {
    @Published var element : SomeElement
    init(element : SomeElement) {
        self.element = element
    }
}
Run Code Online (Sandbox Code Playgroud)
class SomeElement : ObservableObject {
    @Published var value : String
    init(value : String) {
        self.value = value
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以将顶级类(MainThing在本例中)扩展为:

class MainThing : ObservableObject {
    @Published var element : SomeElement
    var cancellable : AnyCancellable?
    init(element : SomeElement) {
        self.element = element
        self.cancellable = self.element.$value.sink(
            receiveValue: { [weak self] _ in
                self?.objectWillChange.send()
            }
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

它从嵌入中获取发布者,并在修改类的ObservableObject属性时将更新发送到本地发布者中。您可以扩展它以使用CombineLatest从多个属性或主题的任意数量的变体发布流。valueSomeElement

但这不是一个“直接做”的解决方案,因为这种模式的逻辑结论是在您增长了视图层次结构之后,您最终将获得订阅该发布者的潜在大量视图样本将会失效并重绘,可能会导致过度的、全面的重绘和相对较差的更新性能。我建议您看看是否可以重构您的视图以使其特定于某个类,并将其与该类匹配,以最大限度地减少 SwiftUI 视图失效的“爆炸半径”。

  • 最后(以及博客文章中)的建议绝对是金科玉律。我正在陷入链式“objectWillChange”调用的兔子洞,但我只需要重构一个视图来获取“@ObservedObject”...谢谢@heckj:) (5认同)

Sir*_*eck 6

如果您需要在此处嵌套可观察对象,这是我能找到的最佳方法。

class ChildModel: ObservableObject {
    
    @Published
    var count = 0
    
}

class ParentModel: ObservableObject {
    
    @Published
    private var childWillChange: Void = ()
    
    let child = ChildModel()
    
    init() {
        child.objectWillChange.assign(to: &$childWillChange)
    }
    
}
Run Code Online (Sandbox Code Playgroud)

您无需订阅子级的 objectWillChange 发布者并触发父级的发布者,而是自动为已发布的属性和父级的 objectWillChange 触发器分配值。


Sor*_*ica 5

嵌套模型在SwiftUI中尚不可用,但是您可以执行以下操作

class Submodel: ObservableObject {
    @Published var count = 0
}

class AppModel: ObservableObject {
    @Published var submodel: Submodel = Submodel()

    var anyCancellable: AnyCancellable? = nil

    init() {
        anyCancellable = submodel.objectWillChange.sink { (_) in
            self.objectWillChange.send()
        }
    } 
}
Run Code Online (Sandbox Code Playgroud)

基本上,您可以AppModel捕获事件Submodel并将其发送到视图

编辑:

如果您不需要SubModel上课,则可以尝试以下方法:

struct Submodel{
    var count = 0
}

class AppModel: ObservableObject {
    @Published var submodel: Submodel = Submodel()
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,这很有帮助!当您说“嵌套模型在 SwiftUI 中尚不工作”时,您确定它们已经计划好了吗? (3认同)
  • 我想补充一点,AnyCancellable 类型是在组合框架中定义的。我想你们99%的人都知道这一点,我必须谷歌...... (3认同)
  • 就我而言,我有一个具有活动更改的 ObservableObject 列表,如果我会沉入嵌套对象中的更改,那么当我只需要刷新一行时,这将触发重新加载整个列表。所以我会冻结 (2认同)