I'd like to extend ObservableObject behavior in SwiftUI to nested classes, and I'm looking for the proper way to do it. It can be done "manually" with Combine, but I imagine there's a much cleaner way to do it using SwiftUI, and I'm hoping you can point me in the right direction. Here's what I mean…
Below is a typical application of ObservableObject to make a View dynamically respond to changes to a reference type. Tapping the button toggles the showText
value, which makes the text appear/disappear on the screen:
import SwiftUI
class MyClass: ObservableObject {
@Published var showText = false
}
struct ContentView: View {
@ObservedObject var instance = MyClass()
var body: some View {
VStack(spacing: 10) {
Button(action: {
print(self.instance.showText)
self.instance.showText.toggle()
}) {
Text("BUTTON").bold().padding()
.foregroundColor(.white)
.background(Color.red)
}
if instance.showText {
Text("Hello, World!")
}
}
}
}
This works fine.
But what about the modification below, where the class containing showText
is an InnerClass
, itself contained in an OuterClass
? The button toggles showText
just fine, but the notification of the value change no longer propagates through the OuterClass
instance to the View, so the View no longer displays the Text at all.
import SwiftUI
class OuterClass: ObservableObject {
@Published var innerInstance = InnerClass()
}
class InnerClass: ObservableObject {
@Published var showText = false
}
struct ContentView: View {
@ObservedObject var outerInstance = OuterClass()
var body: some View {
VStack(spacing: 10) {
Button(action: {
self.outerInstance.innerInstance.showText.toggle()
}) {
Text("BUTTON").bold().padding()
.foregroundColor(.white)
.background(Color.red)
}
if outerInstance.innerInstance.showText {
Text("Hello, World!")
}
}
}
}
What is the elegant fix for this?
Call the publisher explicitly and you'll get notified:
struct ContentView: View {
@ObservedObject var outerInstance = OuterClass()
var body: some View {
VStack(spacing: 10) {
Button(action: {
self.outerInstance.innerInstance.showText.toggle()
// Call the publisher
self.outerInstance.objectWillChange.send()
}) {
Text("BUTTON").bold().padding()
.foregroundColor(.white)
.background(Color.red)
}
if outerInstance.innerInstance.showText {
Text("Hello, World!")
}
}
}
}
it could be done in your model
import Combine // required for AnyCancelable
class OuterClass: ObservableObject {
private let _inner: InnerClass
var innerInstance: InnerClass {
return _inner
}
var store = Set<AnyCancellable>()
init(_ inner: InnerClass) {
_inner = inner
inner.$showText.sink { [weak self] _ in
self?.objectWillChange.send()
}.store(in: &store)
}
}
and how to use in in your example
import SwiftUI
import Combine
class OuterClass: ObservableObject {
private let _inner: InnerClass
var innerInstance: InnerClass {
return _inner
}
var store = Set<AnyCancellable>()
init(_ inner: InnerClass) {
_inner = inner
inner.$showText.sink { [weak self] _ in
self?.objectWillChange.send()
}.store(in: &store)
}
}
class InnerClass: ObservableObject {
@Published var showText = false
}
let inner = InnerClass()
let outer = OuterClass(inner)
struct ContentView: View {
@ObservedObject var outerInstance = outer
var body: some View {
VStack(spacing: 10) {
Button(action: {
self.outerInstance.innerInstance.showText.toggle()
}) {
Text("BUTTON").bold().padding()
.foregroundColor(.white)
.background(Color.red)
}
if outerInstance.innerInstance.showText {
Text("Hello, World!")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
if you like to observe any change in your inner object, just do it!
class OuterClass: ObservableObject {
private let _inner: InnerClass
var innerInstance: InnerClass {
return _inner
}
var store = Set<AnyCancellable>()
init(_ inner: InnerClass) {
_inner = inner
inner.objectWillChange.sink { [weak self] _ in
self?.objectWillChange.send()
}.store(in: &store)
}
}
UPDATE: based on the discussion below
class OuterClass: Combine.ObservableObject {
private let _inner: InnerClass
var innerInstance: InnerClass {
return _inner
}
var store = Set<AnyCancellable>()
init(_ inner: InnerClass) {
_inner = inner
inner.objectWillChange.sink { [weak self] _ in
self?.objectWillChange.send()
}.store(in: &store)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With