嵌套 ObservedObject 中的更改不会更新 UI
Posted
技术标签:
【中文标题】嵌套 ObservedObject 中的更改不会更新 UI【英文标题】:Changes in nested ObservedObject do not updated the UI 【发布时间】:2021-07-15 20:40:33 【问题描述】:当我有一个嵌套的 ObservedObject 时,嵌套对象的已发布属性中的更改不会更新 UI,直到父对象发生某些事情。这是我的代码中的功能、错误(在 SwiftUI 中)还是错误?
这是一个简化的例子。单击父级的开/关按钮会立即更新 UI,但单击子级的开/关按钮在父级更新之前不会更新。
我正在运行 Xcode 12.5.1。
import SwiftUI
class NestedObject: NSObject, ObservableObject
@Published var flag = false
class StateObject: NSObject, ObservableObject
@Published var flag = false
@Published var nestedState = NestedObject()
struct ContentView: View
@ObservedObject var state = StateObject()
var body: some View
VStack
HStack
Text("Parent:")
Button(action:
state.flag.toggle()
, label:
Text(state.flag ? "On" : "Off")
)
HStack
Text("Child:")
Button(action:
state.nestedState.flag.toggle()
, label:
Text(state.nestedState.flag ? "On" : "Off")
)
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView()
【问题讨论】:
【参考方案1】:@ObservedObject
或 @StateObject
在 ObservableObject
更新时更新视图。当 @Published
属性更改或直接调用 objectWillChange.send()
时会发生这种情况。
因此,“正常”(也是最简单的)方法是使用值类型,例如struct
,用于 @Published
属性。
struct NestedObject
var flag = false
这样做的原因是,整个NestedObject
在其属性被修改时会发生变化,因为struct
是一个值类型。相反,引用类型class
在其属性被修改时不会改变(即引用保持不变)。
但是,有时你可能需要它是一个引用类型的对象,因为它可能有自己的生命周期,等等......
在这种情况下,您绝对可以只调用state.objectWillChange.send()
,但这仅在视图启动更改时才有效,而不是在嵌套对象启动更改时。在我看来,这里最好的通用方法是使用嵌套的内部视图,它有自己的@ObservedObject
来观察内部对象的变化:
struct ContentView: View
private struct InnerView: View
@ObservedObject var model: NestedObject
var body: some View
Text("Child:")
Button(action:
model.flag.toggle()
, label:
Text(model.flag ? "On" : "Off")
)
@StateObject var state = OuterObject() // see comments 1, 2 below
var body: some View
VStack
HStack
Text("Parent:")
Button(action:
state.flag.toggle()
, label:
Text(state.flag ? "On" : "Off")
)
HStack
InnerView(model: state.nestedObject)
1。你不应该调用你的类StateObject
,因为它与SwiftUI 的StateObject
属性包装器冲突。我将其重命名为OuterObject
。
2。此外,如果在视图中实例化对象,则应使用@StateObject
而不是@ObservedObject
。
【讨论】:
【参考方案2】:它按预期工作:ObservableObject
仅发送有关 @Published
属性更改的通知,但不传播嵌套 ObservableObjects 的更改通知。
我会遵循advice from New Dev:尽可能使用结构或使用单独的视图来订阅嵌套对象。
如果您确实需要嵌套的 ObservableObjects,您可以使用 Combine 将 objectWillChange
事件从嵌套对象传播到外部对象:
import Combine
import SwiftUI
class InnerObject: ObservableObject
@Published var flag = false
class OuterObject: ObservableObject
@Published var flag = false
var innerObject = InnerObject()
didSet
subscribeToInnerObject()
init()
subscribeToInnerObject()
private var innerObjectSubscription: AnyCancellable?
private func subscribeToInnerObject()
// subscribe to the inner object and propagate the objectWillChange notification if it changes
innerObjectSubscription = innerObject.objectWillChange.sink(receiveValue: objectWillChange.send)
struct ContentView: View
@ObservedObject var state = OuterObject()
var body: some View
VStack
Toggle("Parent \(state.flag ? "On" : "Off")", isOn: $state.flag)
Toggle("Child \(state.innerObject.flag ? "On" : "Off")", isOn: $state.innerObject.flag)
.padding()
【讨论】:
【参考方案3】:感谢您的澄清。对我来说最有价值的一点是,这种行为不是(SwiftUI 的)错误,而是一种设计行为。
SwiftUI(更准确地说是Combine)只能看到值的变化,因此它可以看到@Published struct 实例的属性值变化,但不能看到@Published 类实例的变化。
因此,答案是“如果你想根据那些嵌套对象的属性值的变化来更新 UI,就使用嵌套对象的结构实例。如果你必须使用类实例,使用另一种机制来显式通知变化”。
这里是使用 struct 代替 class 的 NestedObject 的修改代码。
import SwiftUI
struct NestedObject
var flag = false
class OuterObject: NSObject, ObservableObject
@Published var flag = false
@Published var nestedState = NestedObject()
struct ContentView: View
@ObservedObject var state = OuterObject()
var body: some View
VStack
HStack
Text("Parent:")
Button(action:
state.flag.toggle()
, label:
Text(state.flag ? "On" : "Off")
)
HStack
Text("Child:")
Button(action:
state.nestedState.flag.toggle()
, label:
Text(state.nestedState.flag ? "On" : "Off")
)
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView()
【讨论】:
【参考方案4】:试试这个,对我来说在 ios-15、catalyst 15、macos 12 上运行良好,使用 xcode 13:
HStack
Text("Child:")
Button(action:
state.objectWillChange.send() // <--- here
state.nestedState.flag.toggle()
, label:
Text(state.nestedState.flag ? "On" : "Off")
)
【讨论】:
以上是关于嵌套 ObservedObject 中的更改不会更新 UI的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的@ObservedObject 没有在我的视图中执行更改?
当 ObservedObject 更改时,在 SwiftUI 视图中未调用 onReceive
SmartEdit:除非刷新页面,否则嵌套 CMS 组件中的更改不会反映