'SwiftUI Dependency Injection
I have a SwiftUI app, its a tab based app
struct Tab_View: View {
var body: some View {
TabView{
Main1_View().tabItem {
Text("Blah 1")
Image("TabBar1")
}
Main2_View().tabItem {
Text("Blah 2")
Image("TabBar2")
}
}
}
}
Each view has its own view controller
struct Main1_View: View {
@ObservedObject var viewModel: Main1_ViewModel = Main1_ViewModel()
var body: some View {
VStack(spacing:0){
<<< VIEW CODE >>>
}
}
}
ViewModel Example
class Main1_ViewModel: ObservableObject {
@ObservableObject var settings: GameSettings
func Randomise(){
dataSource = settings.selectedFramework;
}
}
The class GameSettings is used by multiple viewmodels, is an ObservableObject, same instance of the class everywhere.
My background is C# and using CastleWindsor for dependency injection.
My Question: Is there a SwiftUI equivalent to pass around an instance of GameSettings?
Solution 1:[1]
Because of your requirement to use an ObservableObject
(GameSettings
) inside another ObservableObject
(the view model) and use dependency injection, things are going to be a little bit convoluted.
In order to get a dependency-injected ObservableObject
to a View
, the normal solution is to use @EnvironmentObject
. But, then you'll have to pass the object from the view to its view model. In my example, I've done that in onAppear
. The side effect is that the object is an optional property on the view model (you could potentially solve this by setting a dummy initial value).
Because nested ObservableObject
s don't work out-of-the box with @Published
types (which work with value types, not reference types), you'll want to make sure that you use objectWillChange
to pass along any changes from GameSettings
to the parent view model, which I've done using Combine
.
The DispatchQueue.main.asyncAfter
part is there just to show that the view does in fact update when the value inside GameSettings
is changed.
(Note, I've also changed your type names to use the Swift conventions of camel case)
import SwiftUI
import Combine
struct ContentView: View {
@StateObject private var settings = GameSettings()
var body: some View {
TabView {
Main1View().tabItem {
Text("Blah 1")
Image("TabBar1")
}
Main2View().tabItem {
Text("Blah 2")
Image("TabBar2")
}
}.environmentObject(settings)
}
}
struct Main1View: View {
@EnvironmentObject var settings: GameSettings
@StateObject var viewModel: Main1ViewModel = Main1ViewModel()
var body: some View {
VStack(spacing:0){
Text("Game settings: \(viewModel.settings?.myValue ?? "no value")")
}.onAppear {
viewModel.settings = settings
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
settings.myValue = "changed"
}
}
}
}
class GameSettings : ObservableObject {
@Published var myValue : String = "Test"
}
class Main1ViewModel: ObservableObject {
private var cancellable : AnyCancellable?
var settings: GameSettings? {
didSet {
print("Running init on Main1ViewModel")
self.objectWillChange.send()
cancellable = settings?.objectWillChange.sink(receiveValue: { _ in
print("Sending...")
self.objectWillChange.send()
})
}
}
}
struct Main2View : View {
var body: some View {
Text("Hello, world!")
}
}
Solution 2:[2]
Dependency injection, DI is the practice of providing an object with the other objects it depends on rather than creating them internally.
My Opinion For SwiftUI you can use @EnvironmentObject. @EnvironmentObject and the View Model Factory both provide a clean solution to this.
Check this tutorial https://mokacoding.com/blog/swiftui-dependency-injection/
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | jnpdx |
Solution 2 | Tariqul |