A Summary Of How To Pass Data To SwiftUI Environment

Posted in SwiftUI

July 4th, 2024

⏱ Reading Time: 4 mins

The SwiftUI environment is an in-memory shared storage among all views within a view hierarchy. Thanks to it, various parts of an app can access shared data easily, allowing for better state management which is fundamental to SwiftUI. The environment simplifies data flow by just making it available to views, while each individual view can read or update only the pieces of data that’s interested in. In general, the environment lifts a big load of sharing data among views, it helps us write cleaner code, but most importantly, it lets us focus more on the views and the user interface.

Since the environment is essential when making SwiftUI apps, it’s crucial to know how to pass custom data to it, as well as how to access that data from within views. There are two kinds of data we can provide to the environment; custom objects and environment values. There are three view modifiers we can use to manage that and there are also particular property wrappers to read all kind of data in views. This post covers all that through quick how-to examples, so just read on.

Passing Observable objects to SwiftUI environment

The most common case of data we provide to the view hierarchy regards objects of observable types. That is, types that hold and manipulate data marked with the @Observable macro. Consider the following simple example:

Suppose that we want a UserInfo instance to be available to all views in the view hierarchy. To make this possible, we need first to create one right before the initialization of the first view in hierarchy that is going to make use of it. Often, that top-level spot of initialization is the app entry point:

If necessary, we can perform any configuration before providing the userInfo object to the environment. For example:

Once the observable object has been initialized and potentially configured, it’s time to inject it to the SwiftUI environment. This is achieved by applying a view modifier to the first view in the hierarchy. When using @Observable instances, the modifier to use is the environment(_:), demonstrated right next:

To access the userInfo and any other object within the view, we have to declare a local property and wrap it with the @Environment property wrapper. In the wrapper we specify the data type of the object we want to access in the environment:

From there on, we can use the userInfo object in the view as needed:

Passing Observable objects in system versions before iOS 17

The @Observable macro is available as of iOS 17 without backwards compatibility, so it’s not available in older system versions. Providing data to the SwiftUI environment when supporting older versions changes a bit, because, even though the workflow remains the same as above, there are different APIs to call in order to achieve that.

Let’s start with the type declaration. This time, the UserInfo must conform to ObservableObject protocol, and any properties that the views’ state depends on should be marked as @Published (usually the majority of properties):

Next, the userInfo instance must be initialized and wrapped with the @StateObject property wrapper, instead of @State as happened before:

What also changes is the view modifier that we use to pass the above object to the SwiftUI environment. This time it’s the environmentObject(_:) modifier:

Lastly, on the view’s part we declare once again a property of our custom type (a UserInfo property here), but this time we wrap it with the @EnvironmentObject property wrapper. After that, we can access the various properties (and methods if exist) of our custom object whenever necessary:

Providing custom environment values to the view hierarchy

SwiftUI provides a predefined collection of environment values to our views which are gathered in a structure called EnvironmentValues. Various view properties that we alter using view modifiers, such as the font type, line limit, preferred color scheme and so on, are actually built-in environment values made available to our views by SwiftUI. But in addition to them, we can define and pass our own custom values to the environment as well.

Before Xcode 16 it has been necessary to define a custom EnvironmentKey type and then extend the EnvironmentValues struct as shown next (read more about all that here):

With the above we expose the userPhrase string property to the view hierarchy as an environment value. In Xcode 16 and above, we can replace the above code just with a simplified EnvironmentValues extension and make use of the @Entry macro:

Injecting the above property to the environment of the view hierarchy is done with the environment(_:_:) view modifier at the top level point, where the first argument is the key path to the custom property and the second is the value we would like to set to it:

To read the custom environment value in any view down in the hierarchy, we can declare a local property and wrap it with the @Environment property wrapper. As argument, we specify the key path to the custom property exactly as it happened in the previous code example. After that, the custom environment property is there to use it according to our needs:

Conclusion

Coming to the end, all the above form the various ways to pass data to the SwiftUI environment. We can inject both custom objects and custom environment values, as long as we use the proper APIs depending on the supported system version. We came across three view modifiers that allow to provide custom data to the environment of a view hierarchy, and equal approaches for accessing that custom data in any SwiftUI view. I hope you found this post helpful. Thank you for reading!

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.