EnvironmentObject vs Singleton in SwiftUI?
You are correct there is no reason in this case to use an EnvironmentObject. Apple even encourages to make no excessive use of EnvironmentObjects.
Nevertheless an EnvironmentObject can be great too, if you use an object in many views, because then you don't have to pass it from View A to B, from B to C and so on.
Often you find yourself in a situation where even @State and @Binding will be enough to share and update data in a view and between two views.
How to create a singleton that I can update in SwiftUI
Make your singleton a ObservableObject
with @Published
properties:
struct ContentView: View {
@StateObject var loading = LoadingSingleton.shared
var body: some View {
if loading.isLoading {
Text("Loading...")
}
ChildView()
Button(action: { loading.isLoading.toggle() }) {
Text("Toggle loading")
}
}
}
struct ChildView : View {
@StateObject var loading = LoadingSingleton.shared
var body: some View {
if loading.isLoading {
Text("Child is loading")
}
}
}
class LoadingSingleton : ObservableObject {
static let shared = LoadingSingleton()
@Published var isLoading = false
private init() { }
}
I should mention that in SwiftUI, it's common to use .environmentObject
to pass a dependency through the view hierarchy rather than using a singleton -- it might be worth looking into.
SwiftUI Classes that conforms ObservableObject should be Singleton?
Why do you think a viewmodel should be a singleton? And especially, why should an ObservableObject
conformant class need a singleton instance? That's a bad idea.
Not only is this absolutely unnecessary, this would also mean you cannot have several instances of the same view on the screen without them having shared state. This is especially bad on iPad if you want to support split screen and running 2 scenes of your app on the screen at the same time.
Don't make anything a singleton, unless you absolutely have to.
The only important thing to keep in mind with storing @ObservedObject
s on SwiftUI View
s is that they should never be initialised inside the view. When an @ObservedObject
changes (or one of its @Published
properties change), the View
storing it will be reloaded. This means that if you create the object inside the View
, whenever the object updates, the view itself will create a new instance of said object.
So this is a bad idea and won't work:
struct ContentView: View {
// Never do this
@ObservedObject private var vm = MyViewModel()
var body: some View {
Text(vm.result)
}
}
Instead, you need to inject the viewmodel into your View
(by creating it in the parent view or in a coordinator, etc, wherever you create your ContentView
from).
struct ParentView: View {
@State private var childVM = MyViewModel()
var body: some View {
ContentView(vm: childVM)
}
}
struct ContentView: View {
@ObservedObject private var vm: MyViewModel
// Proper way of injecting the view model
init(vm: MyViewModel) {
self.vm = vm
}
var body: some View {
Text(vm.result)
}
}
Update UI on Singleton property change without sharing dependency between Views
The ObservableObject
updates view only via ObservedObject/StateObject/EnvironmentObject
(which are actually have same nature). That wrapper (one of them) actually observers changes in published properties and refreshes view.
So your case can be solved like
struct ParentView: View {
@StateObject private var networkNanager = NetworkManager.shared // << observer !!
var body: some View {
SOME_VIEW.disabled(!networkNanager.isConnected) // injects dependency !!
}
}
How does SwiftUI know what environmentObject to assign to which variable
EnvironmentObjects are identified by type.
SwiftUI just looks for environment variables for a specific type (here it's RegisterViewModel
) and uses the first one it finds.
This code puts RegisterViewModel
to the environment:
.environmentObject(RegisterViewModel())
Then, when building a View, SwiftUI looks for a RegisterViewModel
type in the environment:
@EnvironmentObject var viewModel: RegisterViewModel
More details:
- How to set multiple EnvironmentObjects which are same type
- Why can't swiftui distinguish 2 different environment objects?
Related Topics
Adding Items to Array as a Dictionary Value
How to Delete an Item in a Collection View with a Button in the Cell
Why Can't I Mutate a Variable Initially Set to a Certain Parameter When the Func Was Called
Unable to Save On/Off State of a Uitableviewcell
Google API - Invalid Credentials
What's the Best Way to Iterate Over Results from an API, and Know When It's Finished
Label Disappear When Changing Font Size to 25 in Swift
In Swift, Does Int Have a Hidden Initializer That Takes a String
Enumerate Is Unavailable Call the Enumerate Method on the Sequence
How to Convert Hexstring to Bytearray in Swift 3
Create an Outlet in Storyboard to an Inherited Property
How to Send Push Notifications Without Using Firebase Console
Swift Minimum Implementation for Types Conforming to Protocols with Default Implementations
Swift 2 to 3 Migration Dispatch_Get_Global_Queue
Avcapturevideodataoutput Captureoutput Not Being Called
How to Access a Swift Enum Associated Value Outside of a Switch Statement