'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 ObservableObjects 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