1

What is @Environment

 3 years ago
source link: https://sarunw.com/posts/what-is-environment-in-swiftui/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What is @Environment in SwiftUI

15 Jan 2021 ⋅ 6 min read ⋅ SwiftUI Data

What is @Environment

When writing an app, we might need to read a device setting or device environment to adjust our view or behavior to match user needs. Examples of these values are current device locale, device current calendar, color scheme (dark or light), and size class.

In UIKit, this information is scattered among classes, as you can see in the following example.

Value Class Color scheme traitCollection.userInterfaceStyle Locale Locale.current Calendar Calendar.current Horizontal Size Class traitCollection.horizontalSizeClass

In SwiftUI, most of these system-wide settings and variables are accessible via the @Environment property wrapper. You can see all the available options in EnvironmentValues.

Practical Sign in with Apple

Learn everything you need to know about Sign in with Apple to be able to integrate it in your existing app or a new one.

Get it now

How to use @Environment

To access environment values, you create a @Environment variable specifying a keypath to value you want to read and write.

In the following example, we declare four @Environment variables and print them in text views.

struct ContentView: View {
@Environment(\.colorScheme) private var colorScheme
@Environment(\.locale) private var locale
@Environment(\.calendar) private var calendar
@Environment(\.horizontalSizeClass) private var horizontalSizeClass

var body: some View {
VStack {
Text(locale.description)
Text(calendar.description)
Text(horizontalSizeClass == .compact ? "Compact": "Regular")
Text(colorScheme == .dark ? "Dark mode" : "Light mode")
}.font(.headline)
}
}

Here is another example where we adapt our view based on the vertical size class. In an iPhone portrait mode where the vertical size class is .regular, I layout the views in VStack. In a landscape mode where the vertical size class is .compact, I change the layout to HStack.

struct ContentView: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass

var body: some View {
if verticalSizeClass == .regular {
VStack {
Text("Alice")
Text("Bob")
Text("John")
}
.font(.headline)
} else {
HStack {
Text("Alice")
Text("Bob")
Text("John")
}
.font(.headline)
}
}
}

By using @Environment, we can read and adapt to the changes of the environment value.

A view can listen and adapt to environment value changes.A view can listen and adapt to environment value changes.

Override @Environment value

Every environment value has a default value. You don't have to set it before use. Any view can read this value by declaring an @Environment variable with a keypath to the value you want to observe. And if you need to change that, you can do that with minimal afford.

You can set or override some values using the environment(_:_:) view modifier.

To demonstrate that, let's create a color scheme sensitive view, which will change its text and background color according to the color scheme (.colorScheme).

struct ColorSchemeSensitiveView: View {
@Environment(\.colorScheme) private var colorScheme

var body: some View {
Text(colorScheme == .dark ? "Dark" : "Light")
.frame(width: 100, height: 100)
.foregroundColor(colorScheme == .light ? .white : .black)
.background(colorScheme == .light ? Color.black : Color.white)
.cornerRadius(50)
.font(.headline)
}
}

Let's put it into use. We put ColorSchemeSensitiveView into a VStack.

struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
}
}
}

Toggle between dark and light color scheme would result in a change of ColorSchemeSensitiveView appearance. As you can see, ColorSchemeSensitiveView can pick up the color scheme change without the need for its parent (VStack and ContentView) to do anything.

As mentioned before, you can set or override some values using the environment(_:_:) view modifier. In the following example, we override .colorScheme to always .dark.

struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
ColorSchemeSensitiveView()
}
.environment(\.colorScheme, .dark)
}
}

We override the color scheme value to dark, which makes ColorSchemeSensitiveView present as dark appearance regardless of device preference.

ColorSchemeSensitiveView stays in dark appearance even though the device color scheme changeColorSchemeSensitiveView stays in dark appearance even though the device color scheme change

Environment override priority

The environment(_:_:) modifier affects the given view, as well as that view’s descendant views. It has no effect outside the view hierarchy on which you call it.

If we set environment value in many ancestor views, the child's view will read its closest parent value.

Here is an example of ColorSchemeSensitiveView with different environment value override.

struct ContentView: View {
@Environment(\.colorScheme) private var colorScheme

var body: some View {
VStack(spacing: 20) {
ColorSchemeSensitiveView() // <1>
VStack(spacing: 20) {
ColorSchemeSensitiveView() // <2>
.environment(\.colorScheme, .light)
ColorSchemeSensitiveView() // <3>
}.environment(\.colorScheme, .dark)
}
}
}

<1> The first ColorSchemeSensitiveView doesn't have any environment value override, so it picks up the color scheme from the device.
<2> The second ColorSchemeSensitiveView has two environment override, one from its parent and one on its own. The one assigned to itself has higher priority, so ColorSchemeSensitiveView show as .ligth appearance.
<3> The third ColorSchemeSensitiveView get an environment override of .dark from its parent, VStack.

Light appearance

  • The first one will pick up the same color scheme as the device, which is light.
  • The second one getting override by its own .environment(\.colorScheme, .light) and show as light appearance.
  • The last one gets an environment override of dark from its parent, VStack.
Result in light appearanceResult in light appearance

Dark appearance

  • The first one will pick up the same color scheme as the device, which is dark.
  • The second one getting override by its own .environment(\.colorScheme, .light) and show as light appearance.
  • The last one gets an environment override of dark from its parent, VStack.
Result in dark appearanceResult in dark appearance

Result

Conclusion

@Environment is an excellent concept. It exposes an app-wide configuration to a view in a friendly way. It is a singleton, which I think is fine since the name expresses its intention that it is an environment variable. You should expect it to share across the app. Even though it acts in a singleton manner, it also provides a dependency injection capability, making it easy to test and adapt to your own needs.

Even though all the examples of environment value in this article are device settings, @Environment isn't limited to that kind of value. It also includes view values such as lineLimit and font. This makes the environment variable a very powerful concept.

Practical Sign in with Apple

Learn everything you need to know about Sign in with Apple to be able to integrate it in your existing app or a new one.

Get it now

Releated Resources


Get new posts weekly

If you enjoy this article, you can subscribe to the weekly newsletter.

Every Friday, you’ll get a quick recap of all articles and tips posted on this site — entirely for free.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron

Tweet

Share

Previous
Create a list of views in SwiftUI using ForEach

Part 1 in the series "Building Lists and Navigation in SwiftUI". We visit the first building block of any list view, content, and how to create them.

Related Posts

Browse all content by tag

Data in SwiftUI, Part 2: Views as a function of data

Part 2 in a series on understanding data in SwiftUI. We will talk about the key that makes principles in part 1 possible in SwiftUI. And how this resulting in a reduction of the complexity of UI development.

SwiftUI Data
How to create Activity Ring in SwiftUI

A guide to creating an activity-ring-like circular progress bar in SwiftUI. An in-depth tutorial of what I think when making a custom view. At the end of this article, you will be able to create the Activity ring used in the Activity app on Apple Watch.

SwiftUI
Data in SwiftUI, Part 1: Data

Part 1 in a series on understanding data in SwiftUI. In the first part, we will try to understand the importance of data and how they play an essential role in SwiftUI.

SwiftUI Data

← Home


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK