Introducing the SwiftUI Environment

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

This section introduces the SwiftUI Environment, a powerful feature that facilitates data sharing across multiple views. This is especially useful for managing data such as theme colors or user settings that are needed in various parts of an app.

The Challenge: Centralizing Color Management

Consider the budget tracker app, where the text colors for income and expenses are hard-coded in the FinancialEntryRow view. Assume these colors need to be used in other views. You’ll centralize them in the BudgetTrackerApp. This allows the color values to be passed to any view that needs them. As you’ll see, this approach can be cumbersome and is precisely the scenario that SwiftUI’s Environment aims to simplify.

Preparing the Starter Project

Open the starter Xcode project. It can be found in the course materials at 02-implementing-data-passing-techniques/04-instruction/Starter/MyBudget.xcodeproj.

Adding Color Properties to BudgetTrackerApp

Before diving into the code, you should know this section’s goal. Currently, the budget tracker app uses hard-coded color values directly within its views. Although functional, this method lacks flexibility. Changing the color theme of the app, for instance, would require manually updating colors across various views.

let expenseTextColor = Color.red
let incomeTextColor = Color.green
@main
struct BudgetTrackerApp: App {
  // ...

  let expenseTextColor = Color.red
  let incomeTextColor = Color.green

  // ...
}

Propagating the Colors Through the View Hierarchy

Next, pass the centralized color values through the view hierarchy.

struct ContentView: View {
  // ...

  let expenseTextColor: Color
  let incomeTextColor: Color

  // ...
}
@main
struct BudgetTrackerApp: App {
  // ...

  var body: some Scene {
    WindowGroup {
      ContentView(
        entries: entries,
        expenseTextColor: expenseTextColor,
        incomeTextColor: incomeTextColor
      )
    }
  }

  // ...
}
struct FinancialEntryRow: View {
  // ...

  let expenseTextColor: Color
  let incomeTextColor: Color

  // ...
}
struct ContentView: View {
  // ...

  var body: some View {
    NavigationView {
      List {
        Section(header: Text("Entries")) {
          ForEach(entries) { entry in
            FinancialEntryRow(
              entry: entry,
              expenseTextColor: expenseTextColor,
              incomeTextColor: incomeTextColor
            )
          }
        }
      }
      // ...
    }
  }
}
struct FinancialEntryRow: View {
  // ...

  var body: some View {
    HStack {
      // ...
        .foregroundColor(entry.isExpense ?
          expenseTextColor : incomeTextColor)
    }
  }
}
struct FinancialEntryRow: View {
  // ...

  var body: some View {
    HStack {
      Text(entry.isExpense ? "Expense" : "Income")
        .foregroundColor(entry.isExpense ?
          expenseTextColor : incomeTextColor)
      // ...
    }
  }
}

Testing the Changes

Build and run the app to see the modifications.

@main
struct BudgetTrackerApp: App {
  // ...

  let expenseTextColor = Color.orange

  // ...
}

Understanding the SwiftUI Environment

The SwiftUI Environment allows for the central storage and access of shared data. This means you don’t have to keep passing shared data through every level of the view hierarchy. This makes accessing shared data much neater and easier to handle. Data is stored and accessed in the Environment as Environment Values.

Defining Environment Keys

To add custom Environment values, you first define Environment Keys for the data to be shared. These keys act as identifiers to share the data across the app. For the text colors, two Environment keys can be created: one for the expense text color and one for the income text color:

struct ExpenseTextColorKey: EnvironmentKey {
  static let defaultValue: Color = .red
}

struct IncomeTextColorKey: EnvironmentKey {
  static let defaultValue: Color = .green
}

Extending Environment Values

After defining the Environment keys, the next step is to make these keys accessible as properties within SwiftUI’s Environment. This is achieved by extending the EnvironmentValues struct. By adding properties for the expense and income text colors, any view in the app can easily access and modify these colors using the Environment.

extension EnvironmentValues {
  var expenseTextColor: Color {
    get { self[ExpenseTextColorKey.self] }
    set { self[ExpenseTextColorKey.self] = newValue }
  }

  var incomeTextColor: Color {
    get { self[IncomeTextColorKey.self] }
    set { self[IncomeTextColorKey.self] = newValue }
  }
}

Using Environment Values in Views

Once the Environment keys have been defined and EnvironmentValues has been extended, the default values can be accessed using the @Environment property wrapper. For example, in FinancialEntryRow, the text colors can be accessed from the Environment instead of being hard-coded:

struct FinancialEntryRow: View {
  let entry: FinancialEntry

  @Environment(\.expenseTextColor)
  var expenseTextColor
  @Environment(\.incomeTextColor)
  var incomeTextColor

  var body: some View {
    HStack {
      Text(entry.isExpense ? "Expense" : "Income")
        .foregroundColor(entry.isExpense ? expenseTextColor : incomeTextColor)
      Spacer()
      Text("$\(entry.amount, specifier: "%.2f")")
        .foregroundColor(entry.isExpense ? expenseTextColor : incomeTextColor)
    }
  }
}

Overriding Environment Values

Additionally, you can override default values using the Environment view modifier.

WindowGroup {
  ContentView(entries: entries)
    .environment(\.expenseTextColor, Color.orange)
}

Conclusion

In conclusion, the SwiftUI Environment offers a powerful solution for managing shared data in SwiftUI apps. Here’s a summary of the key benefits:

See forum comments
Download course materials from Github
Previous: Demo: Implementing Data Passing in the Budget Tracker App Next: Demo: Simplifying Data Management with SwiftUI Environment