Passing Data in SwiftUI

Jun 20 2024 · Swift 5.9, iOS 17.2, Xcode 15.2

Lesson 01: Fundamentals of Data Passing

Demo

Episode complete

Play next episode

Next
Transcript

Welcome to the first Passing Data in SwiftUI demo. In this demo, you’ll explore the essentials of data flow in SwiftUI using a simple budget tracking app as an example. Time to dive in!

In SwiftUI, understanding data flow is crucial for building dynamic and responsive apps. As you learned in the previous section, data flow refers to how data moves through your app’s view hierarchy, ensuring the UI stays consistent with the underlying data. This includes passing data from one view to another and updating the UI when data changes.

The example budget tracking app for this demo allows users to view their income and expenses. Go ahead and open the MyBudget.xcodeproj Xcode final project for this lesson and open the BudgetTrackerApp.swift file. To see what the app looks like, look at the right side at the live preview. You can see the app’s user interface rendered in the Xcode canvas. The app displays a list of income and expense items.

Now that you’ve seen what the app looks like, it’s time to explore the code to see how data flows through the app’s view hierarchy to render the list of budget entries. For this demo, all the app’s code is in the BudgetTrackerApp.swift file for demonstration purposes only. This makes it easy for you to see how data flows through the whole app. In practice, you would create new files for each view.

Ok, to see how data flows through the app, you’ll walk through all the stages SwiftUI goes through to display the app’s initial UI. First, SwiftUI starts with the entry point of the app. Towards the top of the BudgetTrackerApp.swift file, find the BudgetTrackerApp struct:

@main
struct BudgetTrackerApp: App {
  let entries = [
    FinancialEntry(id: UUID(), amount: 3000, category: "Income", isExpense: false),
    FinancialEntry(id: UUID(), amount: 120, category: "Groceries", isExpense: true),
    FinancialEntry(id: UUID(), amount: 500, category: "Technology", isExpense: true),
    FinancialEntry(id: UUID(), amount: 10, category: "Subscription", isExpense: true)
  ]

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

The BudgetTrackerApp struct is the app’s entry point. Here, the app defines an array of financial entries called entries, each representing an income or expense item. These entries flow through the app’s view hierarchy to display the list of entries starting from here.

The entries array is passed to the ContentView, which is responsible for displaying the entries list. This demonstrates the first aspect of data flow in SwiftUI: data passing.

Below BudgetTrackerApp, you’ll find the ContentView struct:

struct ContentView: View {
  let entries: [FinancialEntry]

  var body: some View {
    NavigationView {
      List {
        Section(header: Text("Entries")) {
          ForEach(entries) { entry in
            FinancialEntryRow(entry: entry)
          }
        }
      }
      .navigationTitle("Budget Tracker")
    }
  }
}

The ContentView struct uses a List to display each financial entry by iterating over the entries array and passing each entry into a FinancialEntryRow intializer, creating a FinancialEntryRow view for each entry.

FinancialEntryRow is declared next in the file as follows:

struct FinancialEntryRow: View {
  let entry: FinancialEntry

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

The FinancialEntryRow view initializer takes a single FinancialEntry as a parameter. This view uses an HStack to display the entry’s category and amount, with conditional formatting based on whether it’s an expense or income. Notice how the isExpense and amount properties from the entry passed into this view are used to decide which Strings are passed into each Text view. For example, take isExpense: If the entry is an expense, "Expense" is passed into the first Text view; otherwise, "Income" is passed into the Text view.

As noted before, Swift autogenerates intializers for structs. This is how you can initialize FinancialEntryRow and ContentView, even though they have no initializers defined.

Putting it all together, this setup illustrates how data is passed down the view hierarchy, via initializers, from the BudgetTrackerApp to ContentView, and then to each FinancialEntryRow. Each view uses the data it receives to render its content, creating a data-driven user interface. This is how SwiftUI renders the initial UI of the app. With this code so far, once the app displays the list of entries, the app can’t change the UI. All the app can do is show the list of entries hard-coded in the app entry point. In Module 2, you’ll improve the example to allow for UI updates like adding new entries.

This concludes the demo. Next, you’ll wrap up the lesson.

See forum comments
Cinema mode Download course materials from Github
Previous: Understanding Data Flow in SwiftUI Next: Conclusion