Defining Custom Environment Keys in SwiftUI

Posted in SwiftUI

October 29th, 2021

⏱ Reading Time: 5 mins

When implementing apps using SwiftUI, we often need to access a view’s environment and read values from it. It’s easy to give environment a picture; think of it as a memory pool containing global values, shared and accessible all the way down to the view hierarchy.

SwiftUI framework sets several values to the environment by default. We can get them by declaring properties marked with the @Environment property wrapper, passing as argument the key path that points to the value we are interested in.

To better demonstrate that, suppose that we have a Text view with the foreground color depending on the device’s current color scheme; different text color for light and dark mode. The key action here is to get the information about the device’s color scheme. Βut how do we do so in SwiftUI?

Color scheme is one of many system related values existing in the views’ environment by default. Τhe following declaration will make it available to any view:

With the colorScheme being available, it’s now easy to specify a different text color for each mode:

Having vital values available in the environment defined by the system is great; however, it would be even greater to have custom keys and values there as well. Let’s find out how to do that.

Specifying custom environment keys

Let’s say that we are implementing a view that displays different content depending on the type of user that is currently connected to the app. For the sake of the example, there are two user types; normal and administrators. Obviously the former should have less privileges or actions available in contrast to administrator users; the latter should have access to all features.

UI for normal and for administrator users side by side

The type of the connected user could be useful in many places throughout the app, not just in that above sample view. Having instant access to it would be extremely handy. For that, we are going to add it to the view hierarchy’s environment.

The process to define new keys in the SwiftUI’s environment is quite standard, and it includes two steps. The first is to define a new custom type, a structure, that conforms to the EnvironmentKey protocol. This protocol has one requirement; to specify the type and the initial value of the key we are creating.

For the give example demonstrated here, this is how we are implementing a new EnvironmentKey type:

The name of the new type can be anything we want, so instead of UserTypeKey you can set a different name. The defaultValue static property is the protocol’s requirement. Obviously, the data type of the value specified here should be anything that fits our needs. In this particular example I’m specifying the Bool data type with its initial value set to false, but it can be anything; either simple Swift types, or custom types, such as classes, structures or enums.

The second step is to define the actual key that we’ll be using in order to access the value in the environment. Along with that, it’s mandatory to provide a getter and setter. We perform that always in the same way; the only thing that gets changed is the custom type in them:

The environment now contains a new key; the isUserAdmin, which is a boolean value indicating the type of user currently connected to the app.

Using the new environment key

Using the new key that we just defined is the same to any other system-defined key in the environment. We have to declare a new property marked with the @Environment property wrapper:

Environment values are by default read-only. We use them without modifying them in order to drive the user interface or other actions in a view; we treat them as if they were normal properties.

For instance, here is how the additional content for administrators is being displayed:

However, it’s possible to update the value of a custom environment key if necessary using a specific view modifier. To see that, let’s add a Toggle view that will make it easy to switch between user types:

isAdmin is a property marked with the @State property wrapper declared in the view like so:

To keep isUserAdmin environment value in sync with the isAdmin state property, we can use the environment(_:_:) view modifier. The first argument is the key path pointing to the custom structure (UserTypeKey here) that contains the value in the environment. The second argument is the new value we want to update it with:

The above do not force view to redraw its contents, so using isUserAdmin in the conditional statement shown previously should be replaced by isAdmin:

Note: Using a state property in the Toggle control is mandatory, as it allows both to read and write its value. On the other hand, environment property’s value is read-only. Using it with the Toggle would be impossible, as the Toggle view would not be able to update its value.

Toggling between normal and administrator user to change the UI

Finally, you may need sometimes to set the initial value of a custom environment key before using it in any view. Most often, after you’ve read it from a file, a database or somewhere else. The best place to do so is in the entry point of the app -the @main struct- by calling the environment(_:_:) view modifier from the main view of your app. Here’s an example:

Conclusion

Setting custom keys and values to the view hierarchy’s environment is a quite standard process. And this post presented all steps included in it. There is no doubt that custom environment keys offer great flexibility in the development workflow. In addition to that, all developers will define custom keys to the environment sooner or later. So, now that you’ve learnt the necessary how-to steps, consider trying that out in your own projects. Thank you for reading, take care!

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.