# ObservableObject

[SwiftUI](/ios/swiftui.md) ⟩ [Data Flow](/ios/swiftui/data-flow.md) ⟩ ObservableObject

{% hint style="success" %}
When a [class](/ios/swift/type/category/basic/class.md) conforms to the <mark style="color:purple;">ObservableObject</mark> [protocol](/ios/swift/type/category/protocol.md), <mark style="color:yellow;">any changes to its</mark> <mark style="color:orange;">published</mark> <mark style="color:yellow;">values</mark> will cause <mark style="color:yellow;">all views using those values to automatically update</mark>, reflecting the changes.
{% endhint %}

```swift
// ⭐️ 1. declare an observable object type
final class ModelData: ObservableObject {   
 
    // ⭐️ 2. declare published properties
    @Published var landmarks: [Landmark] = load("landmarkData.json")               
}

// app
struct LandmarksApp: App {
    
    // ⭐️ 3. initialize an observable object (data model)
    @StateObject private var modelData = ModelData() 
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                // ⭐️ 4. put the model object in the environment.
                // ------------------------------------------------
                //       any subview can access this model object 
                //       through `@EnvironmentObject` automatically. (see 6.)
                .environmentObject(modelData)         
        }
    }
}

// any subview in the view hierarchy
struct LandmarkList: View {

    // ⭐️ 5. adopt the model object as an `@EnvironmentObject`
    // --------------------------------------------------------
    // `modelData` property gets its value AUTOMATICALLY, as long as
    // `environmentObject(_:)` modifier has been applied to a parent view.
    @EnvironmentObject var modelData: ModelData      
    
    var body: some View { ... }
}
```

* An <mark style="color:purple;">observable object</mark> is a <mark style="color:yellow;">custom object</mark> (<mark style="color:orange;">reference type</mark>) for your [data](/ios/master/term/data-model.md) that <mark style="color:yellow;">can be bound to a view</mark> from storage in SwiftUI’s environment.&#x20;

{% tabs %}
{% tab title="👥 相關" %}

* Use <mark style="color:purple;">ObservableObject</mark> to <mark style="color:yellow;">declare</mark> an <mark style="color:purple;">observable object</mark> [type](/ios/swift/type.md).
* Use [@Published](/ios/swiftui/view/state/observable-object/published.md) to <mark style="color:yellow;">declare</mark> [published values](/ios/swiftui/view/state/observable-object/published-value.md) in an <mark style="color:purple;">observable object</mark> type.
* Use [@StateObject](/ios/swiftui/view/state/object/stateobject.md) to <mark style="color:yellow;">initialize</mark> an <mark style="color:purple;">observable object</mark>.
* [observer](/ios/swiftui/view/state/observable-object/observer.md)： <mark style="color:yellow;">any</mark> [view](/ios/swiftui/view.md) or <mark style="color:yellow;">object</mark> that <mark style="color:yellow;">uses the</mark> <mark style="color:purple;">observable object</mark><mark style="color:yellow;">'s data</mark>.
* [data model](/ios/master/term/data-model.md)：data shared with any views in your app.
* [environment object](broken://pages/QCGBkO6BC8iSChrBuMic)：an [observable object](/ios/swiftui/view/state/observable-object/observableobject.md), which is <mark style="color:yellow;">put in the environment</mark> by calling an <mark style="color:orange;">ancester view</mark>'s [.environmentObject(\_:)](https://developer.apple.com/documentation/swiftui/scene/environmentobject\(_:\)) modifier.
  {% endtab %}

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

* [Combine](https://developer.apple.com/documentation/combine) ⟩ [ObservableObject](https://developer.apple.com/documentation/combine/observableobject) (protocol)
* [SwiftUI](https://developer.apple.com/documentation/swiftui) ⟩ [Model data](https://developer.apple.com/documentation/swiftui/model-data) ⟩ [@ObservedObject](https://developer.apple.com/documentation/swiftui/observedobject) (property wrapper)
* SwiftUI Tutorials ⟩ [Handling User Input](https://developer.apple.com/tutorials/swiftui/handling-user-input) ⟩&#x20;
  * Sec. 4: [Use an Observable Object for Storage](https://developer.apple.com/tutorials/swiftui/handling-user-input#Use-an-Observable-Object-for-Storage)
  * Sec. 5: [Adopt the Model Object in Your Views](https://developer.apple.com/tutorials/swiftui/handling-user-input#Adopt-the-Model-Object-in-Your-Views)
* SwiftUI ⟩ [State and Data Flow](https://developer.apple.com/documentation/swiftui/state-and-data-flow)
  * [StateObject](https://developer.apple.com/documentation/swiftui/stateobject)
  * [EnvironmentObject](https://developer.apple.com/documentation/swiftui/environmentobject)
    {% endtab %}

{% tab title="📗 參考" %}

* [ ] Majid ⟩ [The difference between @StateObject, @EnvironmentObject, and @ObservedObject in SwiftUI](https://swiftwithmajid.com/2020/07/02/the-difference-between-stateobject-environmentobject-and-observedobject-in-swiftui/) #todo
  {% endtab %}

{% tab title="💬  討論" %}

* [**SwiftUI View not updating based on @ObservedObject**](https://stackoverflow.com/questions/60956270/swiftui-view-not-updating-based-on-observedobject)
* [**@Published ObservedObjects SwiftUI Updates not Happening**](https://stackoverflow.com/questions/59728852/published-observedobjects-swiftui-updates-not-happening)
  {% endtab %}
  {% endtabs %}

## 問題

在跟 CS193P 到第三課的時候，如果按照課程上的程式碼原封不動的照抄，程式可以正常執行。

但如果擅自將 **Card** 這個 struct 改成不僅遵循 [Identifiable](https://developer.apple.com/documentation/swift/identifiable) 同時也遵循 [Equatable](https://developer.apple.com/documentation/swift/equatable)，這時就出現問題了：「在按卡片的時候，卡片不會翻面」，這問題似乎跟 [ForEach(data){ ... }](https://developer.apple.com/documentation/swiftui/foreach/init\(_:content:\)-6oy5i) 語法裡面，`data` 部分的 .[id](https://developer.apple.com/documentation/swift/identifiable) 有關係。

就算 @Published var **viewModel** 更新了，但在 ForEach(viewModel.**cards**) 裡面的 **cards** 的 **id** 並沒有變更，再加上 **Card** 本身遵循 [Equatable](https://developer.apple.com/documentation/swift/equatable) (這點很重要，因為如果沒有 [Equatable](https://developer.apple.com/documentation/swift/equatable)，程式可正常執行)，造成 SwiftUI 判斷 **EmojiMemoryGameView** 的 body 裡面的 **ForEach** 部分不需更新，因此就不會翻面了。

{% hint style="warning" %}
有 Equatable 跟沒有 Equatable 怎麼差那麼多呢？SwiftUI 是如何判斷一個 view 到底要不要更新呢？
{% endhint %}

## 參考資料

{% tabs %}
{% tab title="🍎 官方" %}

* [SwiftUI](https://developer.apple.com/documentation/swiftui)  ⟩  [View Layout & Presentation](https://developer.apple.com/documentation/swiftui/view-layout-and-presentation)  ⟩  [ForEach](https://developer.apple.com/documentation/swiftui/foreach)
* Swift  ⟩  Standard Library  ⟩  [Basic Behaviors](https://developer.apple.com/documentation/swift/swift_standard_library/basic_behaviors)  ⟩  [Identifiable](https://developer.apple.com/documentation/swift/identifiable)
  {% endtab %}

{% tab title="📘 參考" %}

* [x] [Working with Identifiable items in SwiftUI](https://www.hackingwithswift.com/books/ios-swiftui/working-with-identifiable-items-in-swiftui) - Hacking with Swift
* [x] [How to create views in a loop using ForEach](https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-views-in-a-loop-using-foreach) - Hacking with Swift
* [x] [CS193P: Lecture 3, Reactive UI](https://youtu.be/SIYdYpPXil4) - YouTube
  {% endtab %}
  {% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lochiwei.gitbook.io/ios/swiftui/view/state/observable-object/observableobject.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
