I'm curious, is there a way to implement a singleton @ObservedObject
? I'm trying to find an alternative to the environmentObject
pattern in SwiftUI, and for some situations (eg for an AppState object where there should only be a single instance), I'm experimenting with the idea of using a single shared instance that can be picked up directly in subviews, without any setup from the parents.
This would have the delightful side benefit of easier previews (since the need for .environmentObject(...)
would go away).
Here's some sample code I've been playing with:
// An object to hold App State. There should only ever be one instance.
class AppState : ObservableObject {
@Published var showLabels = false
static var shared: AppState { AppState() }
}
// A subview that could pick up the AppState through static member on the class
struct IdentityPanel: View {
@ObservedObject var appState = AppState.shared
@State private var name = ""
var body: some View {
Section("Identity") {
if appState.showLabels {
Text("Enter your Name here:")
}
TextField("Name", text: $name)
}
}
}
// The parent view, it also accesses the AppState directly, but doesn't
// do any setup for the child.
struct SingletonAppState: View {
@ObservedObject var appState = AppState.shared
var body: some View {
Form {
Toggle("Show Labels", isOn: $appState.showLabels)
IdentityPanel()
}
}
}
// Look, Ma, no .environmentObject!
struct SingletonAppState_Previews: PreviewProvider {
static var previews: some View {
SingletonAppState()
}
}
Now this compiles and everything, but value changes aren't being picked up correctly. Changing the showLabels value in the parent doesn't cause the child to refresh, as I had hoped.
My first thought was that maybe the AppState object needs to be instantiated in a view with a @StateObject
decorator, to have all the required scaffolding set up. So I tried this newer definition for the class:
class AppState : ObservableObject {
@Published var showLabels = false
private struct StateHolder : View {
@StateObject var state = AppState()
var body: some View {
Text("_")
}
}
static private var _stateHolder: StateHolder { StateHolder() }
static var shared: AppState { _stateHolder.state }
}
But that didn't work either. So then I thought that maybe the owner of the State needs to be in the view hierarchy directly, so I changed the decorator in SingletonAppState
from @ObservedObject
to @StateObject
. But that didn't improve things either.
I don't understand what's going on at a lower level to make any more progress. Can someone have any insight in this, please? Hopefully, I've just missed something simple.