> 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/view/state/observable-object/observableobject.md).

# 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 %}
