So what can you do to work around this? If you’re really tied to this nested class object structure, then you change the objects a bit to “manually” support notifying that publisher chain when the property within a nested object has updated, and you want that reflected in a SwiftUI view. If you had replaced the element property with a new instance of SomeElement, then it would trigger the publisher. That’s the benefit (and trouble) with reference semantics – it’s not entirely obvious that something down below that reference was updated, but as a benefit – you’re not having to copy around the world of what’s in there.
When you update something in a class, you’re not updating the reference to the class – the reference stays the same.
We’re dealing with a class here, so we’re in the world of reference semantics. So here’s the kicker to what’s happening: The property wrapper watches for the properties to have changed.
When most folks use this protocol, they’re not creating the publisher – they’re letting the swift compiler do the heavy lifting, which synthesizes the code to create it, and with the property wrapper, to hook up and watch the properties that should trigger it. From there, the code within the SwiftUI framework (which uses it), invalidates any relevant view, and looks up what it needs from the referenced object to display a new view. The idea, as I understand it, is that the publisher is specifically meant to provide a signal that something has changed – but not the details of what changed. Not what I expected when I first uncovered it, and it made me scratch my head. The type aliases for this publisher point to a type signature of Publisher. You can dig around a bit, and you’ll find what it publishes may not be what you expect: it isn’t publishing the values changing, just that something will change. An object that conforms to the observable object protocol has a publisher on it with a specific name: objectWillChange. There are two parts to it, the first of which is the ObservableObject protocol, and the second part is one or more properties on that object that have the property wrapper. This pattern is at the intersection of Combine and SwiftUI, and specific to classes. What are you setting up with your views and models? Can you make them a more aligned to a direct representation? Let me explain what’s happening, how you can work around it, and you can judge for yourself. When you hit this pattern, it’s a good time to step back and look at the bigger picture. You can work around this, and get your view updating with some tweaks to the top level object, but I’m not sure that I’d suggest this as a good practice. I’ve seen this pattern described as “nested observable objects”, and it’s a subtle quirk of SwiftUI and how the Combine ObservableObject protocol works that can be surprising. Let me show you some code, a very simplified representation of this pattern: class MainThing : ObservableObject var element : SomeElementĬlass SomeElement : ObservableObject var value : StringĪnd a view that displays it: struct MainThingView: View var model : MainThingĪt first blush, this looks fine – the view displays, and property within the nested view is shown as you’d expect so what’s the problem? The issue is when you update that nested element’s property, even though it’s listed as the change doesn’t propagate to the view.
When you get into seeing the code for the view, how it’s formed, and what the models look like, you see a pattern appear: nested objects that conform to ObservableObject with a reference from one to another, a top level object passed into the view, and view elements that follow the dot-notation chain to create the display.
Hey, why isn’t my view updating? It shows the initial data, but it doesn’t update when that data gets changed! … more than one person, including me …