Jul 21, 2020

Declarative dependency injection in SwiftUI - 2

Start with part one of this post

In the first part we discussed how SwiftUI declarative syntax made Dependency Injection DI a bit magical and tried to understand the magic. Now let's see how to receive the injected values in your Views and also how to inject your custom types into your View hierarchy.


To read the value from the EnvironmentValues injected by the parent you need to use a property wrapper called Environment

Just pass the KeyPath<EnvironmentValues, Value> of property in the Environment property wrapper's initializer that you want to use in your view. You can ignore writing the type (Font? In this example) as it is inferred from the key path in initializer.


struct ContentView: View {
  @Environment(\.font) var font
    var body: some View {
        Text("Hello world")
            **.font(font)**
    }
}

Easy right? Now lets see how you can inject your custom dependencies, things that are not available in EnvironmentValues

Let's say you have this struct below that you created in your root view and want to make it available to all views in the tree rooted at root view (without obviously passing through initializer of all views)


struct MyDependency {
  var i = 10
}

All the EnvironmentValues of a view hierarchy are stored by a type represented by EnvironmentKey, which is a protocol with just one requirement which is to provide a default value of your dependency. This is so that SwiftUI can use the default value if nothing is injected from any of the view above the view using it. If you don't have a reasonable default you can always use nil (and make the type as an Optional).

So let's create a key for our dependency MyDependency


struct MyDependencyKey: EnvironmentKey {
  static var defaultValue: MyDependency = MyDependency()
}

Now we need to extend EnvironmentValues to add a computed property to access our dependency.


extension EnvironmentValues {
    var myDependency: MyDependency {
        get {
            self[MyDependencyKey.self]
        }
        set {
            self[MyDependencyKey.self] = newValue
        }
    }
}

The subscript syntax above is possible because of custom subscript implementation in EnvironmentValues

subscript<K>(key: K.Type) -> K.Value where K : EnvironmentKey

Now injecting dependency is as easy as how we injected font earlier.


struct ContentView: View {
    var body: some View {
        VStack {
            YourCustomView()
        }
        **.environment(\EnvironmentValues.myDependency, MyDependency(i: 100))**
    }
}

If you want you can also add some syntactic sugar over it


extension View {
    func inject(_ myDependency: MyDependency) -> some View {
        environment(\.myDependency, myDependency)
    }
}

And now usage will be a little more fancier


struct ContentView: View {
    var body: some View {
        VStack {
            YourCustomView()
        }
        .inject(MyDependency(i: 100))
    }
}

That is all.


Share this article
Tagged with: