> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/swiftui/intro/essentials.md).

# SwiftUI Essentials

[SwiftUI](/ios/swiftui.md) ⟩ [intro](/ios/swiftui/intro.md) ⟩ SwiftUI Essentials

{% tabs %}
{% tab title="🖥️ 影片" %}
{% embed url="<https://developer.apple.com/videos/play/wwdc2024/10150/>" %}
SwiftUI Esssentials (WWDC2024)
{% endembed %}
{% endtab %}

{% tab title="🔴 主題" %}

| 時間╱主題                                                                                                             | 程式碼                                                          |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| 2:30 - [Declarative views](https://developer.apple.com/videos/play/wwdc2024/10150/?time=150)                      | :point\_right: [2:30](#id-2-30) tab                          |
| 2:43 - [Declarative views: layout](https://developer.apple.com/videos/play/wwdc2024/10150/?time=163)              | :point\_right: [2:43](#id-2-43) tab                          |
| 2:56 - [Declarative views: list](https://developer.apple.com/videos/play/wwdc2024/10150/?time=176)                | :point\_right: [Pet](#pet), [ContentView](#contentview) tabs |
| 3:07 - [Declarative views: list](https://developer.apple.com/videos/play/wwdc2024/10150/?time=187)                | :point\_right: [Pet](#pet), [ContentView](#contentview) tabs |
| 4:24 - [Declarative and imperative programming](https://developer.apple.com/videos/play/wwdc2024/10150/?time=264) | :point\_right: [Pet](#pet), [ContentView](#contentview) tabs |
| 5:33 - [Layout container](https://developer.apple.com/videos/play/wwdc2024/10150/?time=333)                       | :point\_right: [5:33](#id-5-33) tab                          |
| 5:41 - [Container views](https://developer.apple.com/videos/play/wwdc2024/10150/?time=341)                        | :point\_right: [Pet](#pet), [ContentView](#contentview) tabs |
| 6:23 - [View modifiers](https://developer.apple.com/videos/play/wwdc2024/10150/?time=383)                         | :point\_right: [Pet](#pet), [ContentView](#contentview) tabs |
| 7:14 - [Custom views](https://developer.apple.com/videos/play/wwdc2024/10150/?time=434)                           | :point\_right: [PetRowView](#petrowview), [Pet](#pet) tabs   |
| 7:20 - [Custom views: iteration](https://developer.apple.com/videos/play/wwdc2024/10150/?time=440)                | :point\_right: [PetRowView](#petrowview), [Pet](#pet) tabs   |
| 7:24 - [Custom views: view properties](https://developer.apple.com/videos/play/wwdc2024/10150/?time=444)          | :point\_right: [PetRowView](#petrowview), [Pet](#pet) tabs   |
| 7:34 - [Custom views: complete row view](https://developer.apple.com/videos/play/wwdc2024/10150/?time=454)        | :point\_right: [PetRowView](#petrowview) tab                 |
| 7:41 - [Custom views: input properties](https://developer.apple.com/videos/play/wwdc2024/10150/?time=461)         |                                                              |
| 7:53 - [Custom views: reuse](https://developer.apple.com/videos/play/wwdc2024/10150/?time=473)                    |                                                              |
| {% endtab %}                                                                                                      |                                                              |

{% tab title="📘 手冊" %}

* [SwiftUI Essentials](https://developer.apple.com/videos/play/wwdc2024/10150/) (WWDC2024)
  {% endtab %}
  {% endtabs %}

{% tabs %}
{% tab title="2:30" %}
2:30 - [Declarative views](https://developer.apple.com/videos/play/wwdc2024/10150/?time=150)

```swift
// declarative views
Text("Whiskers")
Image(systemName: "cat.fill")
Button("Give Treat") { /* action */ }
```

{% endtab %}

{% tab title="2:43" %}
2:43 - [Declarative views: layout](https://developer.apple.com/videos/play/wwdc2024/10150/?time=163)

```swift
// layout (container view)
HStack {
   Label("Whiskers", systemImage: "cat.fill")
   Spacer()
   Text("Tightrope walking")
}
```

{% endtab %}

{% tab title="5:33" %}
5:33 - [Layout container](https://developer.apple.com/videos/play/wwdc2024/10150/?time=333)

```swift
// container view (5:33)
HStack {
   Label("Whiskers", systemImage: "cat.fill")
   Spacer()
   Text("Tightrope walking")
}
```

{% endtab %}

{% tab title="7:53" %}
7:53 - [Custom views: reuse](https://developer.apple.com/videos/play/wwdc2024/10150/?time=473)&#x20;

```swift
PetRowView(pet: model.pet(named: "Whiskers"))
PetRowView(pet: model.pet(named: "Roofus"))
PetRowView(pet: model.pet(named: "Bubbles"))
```

{% endtab %}
{% endtabs %}

## Views

{% tabs %}
{% tab title="Views" %}

* ContentView
* PetRowView
* RatingView
* RatingContainerView
* PetListView
  {% endtab %}

{% tab title="ContentView" %}
{% tabs %}
{% tab title="2:56" %}

```swift
struct ContentView: View {

    // view state
    @State private var pets = Pet.samplePets

    var body: some View {
        List(pets) { pet in        // scrollable list
            HStack {
                // 固定的內容
                Label("Whiskers", systemImage: "cat.fill")
                Spacer()
                Text("Tightrope walking")
            }
        }
    }
}
```

{% endtab %}

{% tab title="3:07" %}

```swift
struct ContentView: View {

    // view state
    @State private var pets = Pet.samplePets

    var body: some View {
        List(pets) { pet in
            HStack {
                // 內容由 pet 決定
                Label(pet.name, systemImage: pet.kind.systemImage)
                Spacer()
                Text(pet.trick)
            }
        }
    }
}sw
```

{% endtab %}

{% tab title="4:42" %}

```swift
struct ContentView: View {

    // view state
    @State private var pets = Pet.samplePets    

    var body: some View {
    
        // action button
        Button("Add Pet") {
            // imperative code (4:24)
            pets.append(Pet("Toby", kind: .dog, trick: "WWDC Presenter"))
        }

        // declarative views (2:30)
        List(pets) { pet in
            HStack {
                Label(pet.name, systemImage: pet.kind.systemImage)
                Spacer()
                Text(pet.trick)
            }
        }
        
    }// body
}
```

{% endtab %}

{% tab title="5:41" %}

```swift
struct ContentView: View {
    var body: some View {
    
        // container view (5:41)
        HStack {                             
            Image(whiskers.profileImage)
            
            // container view (5:41)
            VStack(alignment: .leading) {    
                Label("Whiskers", systemImage: "cat.fill")
                Text("Tightrope walking")
            }
            
            Spacer()
        }
        
    }
}
```

{% endtab %}

{% tab title="6:23" %}

```swift
struct ContentView: View {
    var body: some View {
        Image(whiskers.profileImage)
            .clipShape(.circle)        // view modifier
            .shadow(radius: 3)         // view modifier
            .overlay {                 // view modifier
                Circle().stroke(.green, lineWidth: 2)
            }
    }
}
```

{% endtab %}

{% tab title="7:59" %}

```swift
struct ContentView: View {
    var model: PetStore
    var body: some View {
        List(model.allPets) { pet in    // List
            PetRowView(pet: pet)
        }
    }
}
```

{% endtab %}

{% tab title="8:14" %}

```swift
struct ContentView: View {
    var model: PetStore
    var body: some View {
        List {
            ForEach(model.allPets) { pet in    // ForEach
                PetRowView(pet: pet)
            }
        }
    }
}
```

{% endtab %}

{% tab title="8:27" %}

```swift
struct ContentView: View {
    var model: PetStore
    var body: some View {
        List {
            // ⭐️ List composition: sections
            Section("My Pets") {
                ForEach(model.myPets) { pet in
                    PetRowView(pet: pet)
                }
            }
            Section("Other Pets") {
                ForEach(model.otherPets) { pet in
                    PetRowView(pet: pet)
                }
            }
        }
    }
}
```

{% endtab %}

{% tab title="9:31" %}

```swift
struct ContentView: View {

    var model: PetStore
    
    var body: some View {
        List {
            Section("My Pets") {
                ForEach(model.myPets) { pet in
                    row(pet: pet)
                }
            }
            Section("Other Pets") {
                ForEach(model.otherPets) { pet in
                    row(pet: pet)
                }
            }
        }
    }

    // ⭐️ private view prperty
    private func row(pet: Pet) -> some View {
        PetRowView(pet: pet)
            // ⭐️ row action 
            .swipeActions(edge: .leading) {
                Button("Award", systemImage: "trophy") {
                    pet.giveAward()
                }
                .tint(.orange)

                ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name)))
            }
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="PetRowView" %}
{% tabs %}
{% tab title="7:20" %}

```swift
// PetRowView
struct PetRowView: View {
    var body: some View {
        HStack {
        
            Image(whiskers.profileImage)
                .clipShape(.circle)
                .shadow(radius: 3)
                .overlay {
                    Circle()
                        .stroke(.green, lineWidth: 2)
                }

            Text("Whiskers")

            Spacer()
        }
    }
}
```

{% endtab %}

{% tab title="7:24" %}

```swift
// PetRowView (custom view)(7:24, 7:34)
struct PetRowView: View {

    var body: some View {
        HStack {                // container view (5:33)
            
            // ⭐️ pet image
            profileImage        // private view property (7:24)
 
            // pet name + trick
            VStack(alignment: .leading) {    // container view (5:41)
                Text("Whiskers")
                Text("Tightrope walking")
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            Spacer()
        }
    }

    // ⭐️ private view property (7:24)
    private var profileImage: some View {
        Image(whiskers.profileImage)
            .clipShape(.circle)        // view modifier (6:23)
            .shadow(radius: 3)         // view modifier
            .overlay {                 // view modifier
                Circle().stroke(.green, lineWidth: 2)
            }
    }
}
```

{% endtab %}

{% tab title="7:41" %}

```swift
// PetRowView(pet: pet)
struct PetRowView: View {
    
    // ⭐️ input property
    var pet: Pet
    
    var body: some View {
        HStack {
        
            profileImage

            VStack(alignment: .leading) {
                Text(pet.name)
                Text(pet.trick)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            Spacer()
        }
    }

    // private view property
    private var profileImage: some View {
        Image(pet.profileImage)
            .clipShape(.circle)
            .shadow(radius: 3)
            .overlay {
                Circle().stroke(pet.favoriteColor, lineWidth: 2)
            }
    }
}
```

{% endtab %}

{% tab title="9:31" %}

```swift
struct PetRowView: View {

    var pet: Pet
    
    var body: some View {
        HStack {
            profileImage

            VStack(alignment: .leading) {
                HStack(alignment: .firstTextBaseline) {
                    Text(pet.name)

                    // ⭐️ conditional view
                    if pet.hasAward {
                        Image(systemName: "trophy.fill")
                            .foregroundStyle(.orange)
                    }
                }
                Text(pet.trick)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            Spacer()
        }
    }

    private var profileImage: some View {
        Image(pet.profileImage)
            .clipShape(.circle)
            .shadow(radius: 3)
            .overlay {
                Circle().stroke(pet.favoriteColor, lineWidth: 2)
            }
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="RatingView" %}
{% tabs %}
{% tab title="10:57" %}
10:57 - [State changes](https://developer.apple.com/videos/play/wwdc2024/10150/?time=657)

```swift
struct RatingView: View {

    // ⭐️ internal state
    @State var rating: Int = 5

    var body: some View {
        HStack {
        
            // ⭐️ button (-)
            Button("Decrease", systemImage: "minus.circle") {
                rating -= 1
            }
            .disabled(rating == 0)
            .labelStyle(.iconOnly)

            // ⭐️ 
            Text(rating, format: .number.precision(.integerLength(2)))
                .font(.title.bold())

            // ⭐️ button (+)
            Button("Increase", systemImage: "plus.circle") {
                rating += 1
            }
            .disabled(rating == 10)
            .labelStyle(.iconOnly)
        }
    }
}
```

{% endtab %}

{% tab title="11:51" %}
11:51 - [State changes: animation](https://developer.apple.com/videos/play/wwdc2024/10150/?time=711)

```swift
struct RatingView: View {

    @State var rating: Int = 5

    var body: some View {
        HStack {
            Button("Decrease", systemImage: "minus.circle") {
                withAnimation {    // ⭐️ animation
                    rating -= 1
                }
            }
            .disabled(rating == 0)
            .labelStyle(.iconOnly)

            Text(rating, format: .number.precision(.integerLength(2)))
                .font(.title.bold())

            Button("Increase", systemImage: "plus.circle") {
                withAnimation {    // ⭐️ animation
                    rating += 1
                }
            }
            .disabled(rating == 10)
            .labelStyle(.iconOnly)
        }
    }
}
```

{% endtab %}

{% tab title="12:05" %}
12:05 - [State changes: text content transition](https://developer.apple.com/videos/play/wwdc2024/10150/?time=725)

```swift
struct RatingView: View {

    @State var rating: Int = 5

    var body: some View {
        HStack {
            Button("Decrease", systemImage: "minus.circle") {
                withAnimation {
                    rating -= 1
                }
            }
            .disabled(rating == 0)
            .labelStyle(.iconOnly)

            Text(rating, format: .number.precision(.integerLength(2)))
                // ⭐️ text content transition
                .contentTransition(.numericText(value: Double(rating)))
                .font(.title.bold())

            Button("Increase", systemImage: "plus.circle") {
                withAnimation {
                    rating += 1
                }
            }
            .disabled(rating == 10)
            .labelStyle(.iconOnly)
        }
    }
}
```

{% endtab %}

{% tab title="12:45" %}
12:45 - [State changes: state and binding](https://developer.apple.com/videos/play/wwdc2024/10150/?time=765)

```swift
struct RatingView: View {

    // ⭐️ external binding
    @Binding var rating: Int

    var body: some View {
        HStack {
            Button("Decrease", systemImage: "minus.circle") {
                withAnimation {
                    rating -= 1
                }
            }
            .disabled(rating == 0)
            .labelStyle(.iconOnly)

            Text(rating, format: .number.precision(.integerLength(2)))
                .contentTransition(.numericText(value: Double(rating)))
                .font(.title.bold())

            Button("Increase", systemImage: "plus.circle") {
                withAnimation {
                    rating += 1
                }
            }
            .disabled(rating == 10)
            .labelStyle(.iconOnly)
        }
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="RatingContainerView" %}
{% tabs %}
{% tab title="12:22" %}
12:22 - [State changes: multiple state](https://developer.apple.com/videos/play/wwdc2024/10150/?time=742)\
12:45 - [State changes: state and binding](https://developer.apple.com/videos/play/wwdc2024/10150/?time=765)

```swift
struct RatingContainerView: View {

    // ⭐️ internal state
    @State private var rating: Int = 5

    var body: some View {
        Gauge(value: Double(rating), in: 0...10) {
            Text("Rating")
        }
        RatingView()
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="PetListView" %}
{% tabs %}
{% tab title="15:19" %}
15:19 - [Searchable](https://developer.apple.com/videos/play/wwdc2024/10150/?time=919)

```swift
struct PetListView: View {

    // ⭐️ 
    @Bindable var viewModel: PetStoreViewModel

    var body: some View {
        List {
            Section("My Pets") {
                ForEach(viewModel.myPets) { pet in
                    row(pet: pet)
                }
            }
            Section("Other Pets") {
                ForEach(viewModel.otherPets) { pet in
                    row(pet: pet)
                }
            }
        }
        // ⭐️ searchable
        .searchable(text: $viewModel.searchText)
    }

    private func row(pet: Pet) -> some View {
        PetRowView(pet: pet)
            .swipeActions(edge: .leading) {
                Button("Reward", systemImage: "trophy") {
                    pet.giveAward()
                }
                .tint(.orange)

                ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name)))
            }
    }
}
```

{% endtab %}

{% tab title="15:20" %}
15:20 - [Searchable: customization](https://developer.apple.com/videos/play/wwdc2024/10150/?time=920)

```swift
struct PetListView: View {

    @Bindable var viewModel: PetStoreViewModel

    var body: some View {
        List {
            Section("My Pets") {
                ForEach(viewModel.myPets) { pet in
                    row(pet: pet)
                }
            }
            Section("Other Pets") {
                ForEach(viewModel.otherPets) { pet in
                    row(pet: pet)
                }
            }
        }
        // ⭐️ searchable: customization
        .searchable(text: $viewModel.searchText, editableTokens: $viewModel.searchTokens) { $token in
            Label(token.kind.name, systemImage: token.kind.systemImage)
        }
        // ⭐️ searchable: customization
        .searchScopes($viewModel.searchScope) {
            Text("All Pets").tag(PetStoreViewModel.SearchScope.allPets)
            Text("My Pets").tag(PetStoreViewModel.SearchScope.myPets)
            Text("Other Pets").tag(PetStoreViewModel.SearchScope.otherPets)
        }
        // ⭐️ searchable: customization
        .searchSuggestions {
            PetSearchSuggestions(viewModel: viewModel)
        }
    }

    private func row(pet: Pet) -> some View {
        PetRowView(pet: pet)
            .swipeActions(edge: .leading) {
                Button("Reward", systemImage: "trophy") {
                    pet.giveAward()
                }
                .tint(.orange)

                ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name)))
            }
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Data Types

{% tabs %}
{% tab title="Types" %}

* Pet
* PetStore
* PetStoreViewModel
  {% endtab %}

{% tab title="Pet" %}
{% tabs %}
{% tab title="2:56" %}

```swift
// ⭐️ whiskers (global constant)
let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers")

// ⭐️ Pet
struct Pet: Identifiable {

    // -------- ⭐️ Pet.Kind --------
    enum Kind {
    
        // enum cases
        case cat
        case dog
        case fish
        case bird
        case lizard
        case turtle
        case rabbit
        case bug

        // ⭐️ pet.kind.systemImage
        var systemImage: String {
            switch self {
                case .cat: return "cat.fill"
                case .dog: return "dog.fill"
                case .fish: return "fish.fill"
                case .bird: return "bird.fill"
                case .lizard: return "lizard.fill"
                case .turtle: return "tortoise.fill"
                case .rabbit: return "rabbit.fill"
                case .bug: return "ant.fill"
            }
        }
        
    }// enum Kind

    // -------- pet properties --------
    let id = UUID()            // pet.id ⭐️ 
    var name: String           // pet.name
    var kind: Kind             // pet.kind
    var trick: String          // pet.trick
    var profileImage: String   // pet.profileImage

    // -------- init --------
    // ⭐️ Pet("name", .cat, trick: "trick", profileImage: "imageName")
    init(_ name: String, kind: Kind, trick: String, profileImage: String) {
        self.name = name
        self.kind = kind
        self.trick = trick
        self.profileImage = profileImage
    }
}
```

{% endtab %}

{% tab title="7:41" %}

```swift
import SwiftUI    // for Color ⭐️

// Pet
struct Pet: Identifiable {

    enum Kind {
    
        case cat
        case dog
        case fish
        case bird
        case lizard
        case turtle
        case rabbit
        case bug

        var systemImage: String {
            switch self {
                case .cat: return "cat.fill"
                case .dog: return "dog.fill"
                case .fish: return "fish.fill"
                case .bird: return "bird.fill"
                case .lizard: return "lizard.fill"
                case .turtle: return "tortoise.fill"
                case .rabbit: return "rabbit.fill"
                case .bug: return "ant.fill"
            }
        }
    }

    let id = UUID()
    var name: String
    var kind: Kind
    var trick: String
    var profileImage: String
    var favoriteColor: Color    // ⭐️ new prperty (7:41)

    // ⭐️ new initializer (7:41)
    init(
        _ name: String, 
        kind: Kind, 
        trick: String, 
        profileImage: String, 
        favoriteColor: Color    // ⭐️ new prperty (7:41)
    ) {
        self.name = name
        self.kind = kind
        self.trick = trick
        self.profileImage = profileImage
        self.favoriteColor = favoriteColor
    }
}
```

{% endtab %}

{% tab title="9:31" %}

```swift
@Observable
class Pet: Identifiable {

    // Pet.Kind
    enum Kind {
    
        case cat
        case dog
        case fish
        case bird
        case lizard
        case turtle
        case rabbit
        case bug

        var systemImage: String {
            switch self {
                case .cat: return "cat.fill"
                case .dog: return "dog.fill"
                case .fish: return "fish.fill"
                case .bird: return "bird.fill"
                case .lizard: return "lizard.fill"
                case .turtle: return "tortoise.fill"
                case .rabbit: return "rabbit.fill"
                case .bug: return "ant.fill"
            }
        }
    }

    var name: String
    var kind: Kind
    var trick: String
    var profileImage: String
    var favoriteColor: Color
    var hasAward: Bool = false    // ⭐️ new prperty (9:31)

    init(_ name: String, kind: Kind, trick: String, profileImage: String, favoriteColor: Color) {
        self.name = name
        self.kind = kind
        self.trick = trick
        self.profileImage = profileImage
        self.favoriteColor = favoriteColor
    }

    // ⭐️ new method (9:31)
    func giveAward() {
        hasAward = true
    }
}

// ⭐️ 
extension Pet: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        ProxyRepresentation { $0.name }
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="PetStore" %}
{% tabs %}
{% tab title="7:59" %}

```swift
// 7:59, 8:14
@Observable
class PetStore {
    var allPets: [Pet] = [
        Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green),
        Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue),
        Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange),
        Pet("Mango", kind: .bird,  trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green),
        Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple),
        Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown),
        Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange)
    ]
}
```

{% endtab %}

{% tab title="8:27" %}

```swift
// 8:27, 9:31
@Observable
class PetStore {
    
    var myPets: [Pet] = [
        Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue),
        Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown),
    ]

    var otherPets: [Pet] = [
        Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green),
        Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange),
        Pet("Mango", kind: .bird,  trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green),
        Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple),
        Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange)
    ]
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="PetStoreViewModel" %}
{% tabs %}
{% tab title="15:19" %}
15:19 - [Searchable](https://developer.apple.com/videos/play/wwdc2024/10150/?time=919)

```swift
@Observable
class PetStoreViewModel {

    var petStore: PetStore
    var searchText: String = ""

    init(petStore: PetStore) {
        self.petStore = petStore
    }

    var myPets: [Pet] {
        // For illustration purposes only. The filtered pets should be cached.
        petStore.myPets.filter { searchText.isEmpty || $0.name.contains(searchText) }
    }
    var otherPets: [Pet] {
        // For illustration purposes only. The filtered pets should be cached.
        petStore.otherPets.filter { searchText.isEmpty || $0.name.contains(searchText) }
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## App Definition

{% tabs %}
{% tab title="App" %}
{% tabs %}
{% tab title="16:58" %}
16:58 - [App definition](https://developer.apple.com/videos/play/wwdc2024/10150/?time=1018)

```swift
@main
struct SwiftUIEssentialsApp: App {
    var body: some Scene {
        // ⭐️ app definition
        WindowGroup {
            ContentView()
        }
    }
}
```

{% endtab %}

{% tab title="17:15" %}
17:15 - [App definition: multiple scenes](https://developer.apple.com/videos/play/wwdc2024/10150/?time=1035)

```swift
@main
struct SwiftUIEssentialsApp: App {
    var body: some Scene {
        
        // ⭐️ multiple scenes
        WindowGroup {
            ContentView()
        }

        // ⭐️ multiple scenes
        WindowGroup("Training History", id: "history", for: TrainingHistory.ID.self) { $id in
            TrainingHistoryView(historyID: id)
        }

        // ⭐️ multiple scenes
        WindowGroup("Pet Detail", id: "detail", for: Pet.ID.self) { $id in
            PetDetailView(petID: id)
        }
   }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Widget" %}
{% tabs %}
{% tab title="17:23" %}
17:23 - [Widgets](https://developer.apple.com/videos/play/wwdc2024/10150/?time=1043)

```swift
struct ScoreboardWidget: Widget {
    var body: some WidgetConfiguration {
        // ...
    }
}

struct ScoreboardWidgetView: View {

    var petTrick: PetTrick
    
    var body: some View {
        ScoreCard(rating: petTrick.rating)
            .overlay(alignment: .bottom) {
                Text(petTrick.pet.name)
                    .padding()
            }
            .widgetURL(petTrick.pet.url)
    }
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="Apple Watch" %}
19:37 - [Digital Crown rotation](https://developer.apple.com/videos/play/wwdc2024/10150/?time=1177)

```swift
ScoreCardStack(rating: $rating)
   .focusable()
   #if os(watchOS)
   .digitalCrownRotation($rating, from: 0, through: 10)
   #endif
```

{% endtab %}
{% endtabs %}
