← All daysDay 7 of 49 · Part 2: Getting started with SwiftUI
Day 7

How a view works: body and some View

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 8

How a view works: body and some View

A SwiftUI view is a struct that conforms to View and has a body property. The struct is not the thing on screen. It is a lightweight description of what should be on screen. SwiftUI calls body to get that description, compares it with the previous one, and changes the real interface to match. Because of this, view structs are created and thrown away constantly, and body may run many times. It must be fast, and it must not cause side effects.

For someone coming from UIKit, the consequences are these. There is no view controller, no viewDidLoad, and no stored reference to a view that you later mutate. A view's init is not a lifecycle event, because the struct is re-created whenever its parent re-renders. You change what is on screen by changing state, and SwiftUI calls body again.

12 34 1. body: recomputed when state changes 2. state: the single source of truth 3. modifiers: each wraps the view before it 4. declarative: you describe the UI; SwiftUI draws it the storyboard (no longer required)
The parts of a SwiftUI view
Exercise

Add let _ = print("body ran") as the first line of a view's body, use the app, and count how often it prints. Add a print to the view's init as well. Explain each line of output.

After this lesson you should be able to answer
  1. Why is a SwiftUI view a struct rather than a class?
  2. When does SwiftUI call body?
  3. Why is doing work in a view's init a mistake, and where should that work go instead?
Resources
Find the bug
Interview problem · Part 2

The analytics event that fired forty times

“Product says profile_viewed is being logged dozens of times per visit, and the network inspector shows duplicate requests. Here is the view. What is wrong, and where should this work live?”

struct ProfileView: View {
    let userID: String
    @State private var profile: Profile?

    init(userID: String) {
        self.userID = userID
        Analytics.log("profile_viewed")
        Task { await ProfileStore.shared.prefetch(userID) }
    }

    var body: some View {
        Text(profile?.name ?? "Loading…")
    }
}

Where this comes from: Hacking with Swift: iOS interview questions — from Hacking with Swift's question list: “What's the difference between a view's initializer and onAppear()?”

Show the answer

A SwiftUI view is a cheap description, not an object with a life of its own. Its init runs every time a parent's body is re-evaluated — which is constantly. So the event is logged on every re-render, and each run starts an unstructured Task that nobody owns or cancels. init is not viewDidLoad.

struct ProfileViewFixed: View {
    let userID: String
    @State private var profile: Profile?

    var body: some View {
        Text(profile?.name ?? "Loading…")
            .task(id: userID) {
                Analytics.log("profile_viewed")
                profile = await ProfileStore.shared.load(userID)
            }
    }
}

.task(id:) ties the work to the time the view is on screen: it starts when the view appears, is cancelled when the view goes away, and restarts if userID changes.

What interviewers listen for: That you separate a view's value (re-created endlessly) from its identity (what actually persists), and that you reach for .task rather than a bare Task.