The @Environment property wrapper in SwiftUI gives access to shared values across your app. Apple provides a wide range of values that are built-in to every app. These cover accessibility, layout, colours, and more. The system automatically updates the built-in environment values so that your app knows more about the environment it is running in.
Lets take a look at examples of the available @Environment values. The best way to explain is by diving into the code.
Custom Environment Values
private struct IsPremiumUserKey: EnvironmentKey {
static let defaultValue: Bool = false
}
extension EnvironmentValues {
var isPremiumUser: Bool {
get { self[IsPremiumUserKey.self] }
set { self[IsPremiumUserKey.self] = newValue }
}
}
Later on in the tutorial we will store a custom Environment values. Be careful when using custom @Environment values. Although you can store various information in there, it probably doesn’t make sense for many things. In the example code above, we’re storing that the user is a premium user or not. Because this could affect how a view loads, it probably belongs in the environment given that many views could potentially access it.
Creating a View to show @Environment Values
The code in the example below shows how to access a number of the available @Environment values. One missing is the modelContext which is available as part of SwiftData. You can visit this tutorial about creating a To-Do list with SwiftUI using SwiftData that includes the modelContext @Environment value.
struct ContentView: View {
// Built-in Environment values
@Environment(\.colorScheme) var colorScheme
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.verticalSizeClass) var verticalSizeClass
@Environment(\.layoutDirection) var layoutDirection
@Environment(\.locale) var locale
@Environment(\.calendar) var calendar
@Environment(\.timeZone) var timeZone
@Environment(\.scenePhase) var scenePhase
@Environment(\.presentationMode) var presentationMode
@Environment(\.dismiss) var dismiss
@Environment(\.isEnabled) var isEnabled
@Environment(\.accessibilityReduceMotion) var reduceMotion
@Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor
@Environment(\.accessibilityInvertColors) var invertColors
@Environment(\.openURL) var openURL
@Environment(\.undoManager) var undoManager
@Environment(\.defaultMinListRowHeight) var defaultMinRowHeight
// Custom Environment value
@Environment(\.isPremiumUser) var isPremiumUser
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Text("Built-in @Environment Values")
.font(.title2.bold())
Text("Color Scheme: \(colorScheme == .dark ? "Dark" : "Light")")
if let hSize = horizontalSizeClass {
Text("Horizontal Size Class: \(hSize == .compact ? "Compact" : "Regular")")
} else {
Text("Horizontal Size Class: nil")
}
if let vSize = verticalSizeClass {
Text("Vertical Size Class: \(vSize == .compact ? "Compact" : "Regular")")
} else {
Text("Vertical Size Class: nil")
}
Text("Layout Direction: \(layoutDirection == .leftToRight ? "LTR" : "RTL")")
Text("Locale: \(locale.identifier)")
Text("Calendar: \(calendar.identifier)")
Text("Time Zone: \(timeZone.identifier)")
Text("Is Enabled: \(isEnabled.description)")
Text("Reduce Motion: \(reduceMotion ? "Yes" : "No")")
Text("Differentiate Without Color: \(differentiateWithoutColor ? "Yes" : "No")")
Text("Invert Colors: \(invertColors ? "Yes" : "No")")
Text("Default Min Row Height: \(defaultMinRowHeight)")
Text("Undo Manager Available: \(undoManager != nil ? "Yes" : "No")")
Divider()
Text("Scene Phase: \(scenePhaseString)")
.onChange(of: scenePhase) { oldValue, newValue in
print("Scene phase changed from \(oldValue) to \(newValue)")
}
Divider()
Button("Open Apple.com") {
openURL(URL(string: "https://apple.com")!)
}
Button("Dismiss (if modal)") {
dismiss()
}
Divider()
Text("Custom: Is Premium User: \(isPremiumUser ? "Yes" : "No")")
}
.padding()
}
.navigationTitle("Environment Demo")
}
private var scenePhaseString: String {
switch scenePhase {
case .active: return "Active"
case .inactive: return "Inactive"
case .background: return "Background"
@unknown default: return "Unknown"
}
}
}
On lines 3 to 19 I have included a number of the built in @Environment values. Using them follows the same pattern where you specify the value with \.name such as \.colorScheme, and then provide it a name that you will access it with.
Line 22 includes our isPremiumUser custom environment value.
The body uses a ScrollView and VStack to show the data and is simply a matter of displaying the value such as the Horizontal Size Class that can be used for making a responsive SwiftUI layout.
The colorScheme environment value lets your UI know if the phone is in dark or light mode. You may use this to support a colour palette for a custom dark mode.
Scene Phase on lines 60-63 is useful as it lets you know what phase your app is currently in. When you are using the app it would show the scene as active, but it can change to inactive or background. This is particularly useful if you want to save the state of the app when it goes into background or inactive. You might also opt to stop something running in the background and then restart it back up when the view becomes active.
On line 77, we have our custom premium user @Environment value. Like other environment values, this one could be useful if you offer a premium mode. We haven’t yet set this environment value, but in terms of viewing the app in the preview, the following would be sufficient.
#Preview {
NavigationView {
ContentView()
.environment(\.isPremiumUser, true)
}
}
Here, we set it in the preview. Obviously we wont use this when the app runs in the simulator or on a device. When using the app live, you would need somewhere to set that @Environment value to true such as in a ViewModel when a person has succesfully subscribed to the app.
I hope this helps you see how @Environment can be used. You will probably use a number of these throughout your app. I suggest looking into the accessibility values as they can open your app up to an audience that would benefit from having adjusted views.
Leave a Reply
You must be logged in to post a comment.